aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2018-12-07 14:47:34 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2019-05-08 17:02:32 +0200
commitc4628a3ad4d3c5f65782b152b771bf80357235d6 (patch)
tree8d6e85e33bb1e821ad9dae5b1701cb65f1d0414c /src/libmsc
parent56f90132b8d7d6a40cc1665b34ff35c62becb2f0 (diff)
large refactoring: support inter-BSC and inter-MSC Handover
3GPP TS 49.008 '4.3 Roles of MSC-A, MSC-I and MSC-T' defines distinct roles: - MSC-A is responsible for managing subscribers, - MSC-I is the gateway to the RAN. - MSC-T is a second transitory gateway to another RAN during Handover. After inter-MSC Handover, the MSC-I is handled by a remote MSC instance, while the original MSC-A retains the responsibility of subscriber management. MSC-T exists in this patch but is not yet used, since Handover is only prepared for, not yet implemented. Facilitate Inter-MSC and inter-BSC Handover by the same internal split of MSC roles. Compared to inter-MSC Handover, mere inter-BSC has the obvious simplifications: - all of MSC-A, MSC-I and MSC-T roles will be served by the same osmo-msc instance, - messages between MSC-A and MSC-{I,T} don't need to be routed via E-interface (GSUP), - no call routing between MSC-A and -I via MNCC necessary. This is the largest code bomb I have submitted, ever. Out of principle, I apologize to everyone trying to read this as a whole. Unfortunately, I see no sense in trying to split this patch into smaller bits. It would be a huge amount of work to introduce these changes in separate chunks, especially if each should in turn be useful and pass all test suites. So, unfortunately, we are stuck with this code bomb. The following are some details and rationale for this rather huge refactoring: * separate MSC subscriber management from ran_conn struct ran_conn is reduced from the pivotal subscriber management entity it has been so far to a mere storage for an SCCP connection ID and an MSC subscriber reference. The new pivotal subscriber management entity is struct msc_a -- struct msub lists the msc_a, msc_i, msc_t roles, the vast majority of code paths however use msc_a, since MSC-A is where all the interesting stuff happens. Before handover, msc_i is an FSM implementation that encodes to the local ran_conn. After inter-MSC Handover, msc_i is a compatible but different FSM implementation that instead forwards via/from GSUP. Same goes for the msc_a struct: if osmo-msc is the MSC-I "RAN proxy" for a remote MSC-A role, the msc_a->fi is an FSM implementation that merely forwards via/from GSUP. * New SCCP implementation for RAN access To be able to forward BSSAP and RANAP messages via the GSUP interface, the individual message layers need to be cleanly separated. The IuCS implementation used until now (iu_client from libosmo-ranap) did not provide this level of separation, and needed a complete rewrite. It was trivial to implement this in such a way that both BSSAP and RANAP can be handled by the same SCCP code, hence the new SCCP-RAN layer also replaces BSSAP handling. sccp_ran.h: struct sccp_ran_inst provides an abstract handler for incoming RAN connections. A set of callback functions provides implementation specific details. * RAN Abstraction (BSSAP vs. RANAP) The common SCCP implementation did set the theme for the remaining refactoring: make all other MSC code paths entirely RAN-implementation-agnostic. ran_infra.c provides data structures that list RAN implementation specifics, from logging to RAN de-/encoding to SCCP callbacks and timers. A ran_infra pointer hence allows complete abstraction of RAN implementations: - managing connected RAN peers (BSC, RNC) in ran_peer.c, - classifying and de-/encoding RAN PDUs, - recording connected LACs and cell IDs and sending out Paging requests to matching RAN peers. * RAN RESET now also for RANAP ran_peer.c absorbs the reset_fsm from a_reset.c; in consequence, RANAP also supports proper RESET semantics now. Hence osmo-hnbgw now also needs to provide proper RESET handling, which it so far duly ignores. (TODO) * RAN de-/encoding abstraction The RAN abstraction mentioned above serves not only to separate RANAP and BSSAP implementations transparently, but also to be able to optionally handle RAN on distinct levels. Before Handover, all RAN messages are handled by the MSC-A role. However, after an inter-MSC Handover, a standalone MSC-I will need to decode RAN PDUs, at least in order to manage Assignment of RTP streams between BSS/RNC and MNCC call forwarding. ran_msg.h provides a common API with abstraction for: - receiving events from RAN, i.e. passing RAN decode from the BSC/RNC and MS/UE: struct ran_dec_msg represents RAN messages decoded from either BSSMAP or RANAP; - sending RAN events: ran_enc_msg is the counterpart to compose RAN messages that should be encoded to either BSSMAP or RANAP and passed down to the BSC/RNC and MS/UE. The RAN-specific implementations are completely contained by ran_msg_a.c and ran_msg_iu.c. In particular, Assignment and Ciphering have so far been distinct code paths for BSSAP and RANAP, with switch(via_ran){...} statements all over the place. Using RAN_DEC_* and RAN_ENC_* abstractions, these are now completely unified. Note that SGs does not qualify for RAN abstraction: the SGs interface always remains with the MSC-A role, and SGs messages follow quite distinct semantics from the fairly similar GERAN and UTRAN. * MGW and RTP stream management So far, managing MGW endpoints via MGCP was tightly glued in-between GSM-04.08-CC on the one and MNCC on the other side. Prepare for switching RTP streams between different RAN peers by moving to object-oriented implementations: implement struct call_leg and struct rtp_stream with distinct FSMs each. For MGW communication, use the osmo_mgcpc_ep API that has originated from osmo-bsc and recently moved to libosmo-mgcp-client for this purpose. Instead of implementing a sequence of events with code duplication for the RAN and CN sides, the idea is to manage each RTP stream separately by firing and receiving events as soon as codecs and RTP ports are negotiated, and letting the individual FSMs take care of the MGW management "asynchronously". The caller provides event IDs and an FSM instance that should be notified of RTP stream setup progress. Hence it becomes possible to reconnect RTP streams from one GSM-04.08-CC to another (inter-BSC Handover) or between CC and MNCC RTP peers (inter-MSC Handover) without duplicating the MGCP code for each transition. The number of FSM implementations used for MGCP handling may seem a bit of an overkill. But in fact, the number of perspectives on RTP forwarding are far from trivial: - an MGW endpoint is an entity with N connections, and MGCP "sessions" for configuring them by talking to the MGW; - an RTP stream is a remote peer connected to one of the endpoint's connections, which is asynchronously notified of codec and RTP port choices; - a call leg is the higher level view on either an MT or MO side of a voice call, a combination of two RTP streams to forward between two remote peers. BSC MGW PBX CI CI [MGW-endpoint] [--rtp_stream--] [--rtp_stream--] [----------------call_leg----------------] * Use counts Introduce using the new osmo_use_count API added to libosmocore for this purpose. Each use token has a distinct name in the logging, which can be a globally constant name or ad-hoc, like the local __func__ string constant. Use in the new struct msc_a, as well as change vlr_subscr to the new osmo_use_count API. * FSM Timeouts Introduce using the new osmo_tdef API, which provides a common VTY implementation for all timer numbers, and FSM state transitions with the correct timeout. Originated in osmo-bsc, recently moved to libosmocore. Depends: Ife31e6798b4e728a23913179e346552a7dd338c0 (libosmocore) Ib9af67b100c4583342a2103669732dab2e577b04 (libosmocore) Id617265337f09dfb6ddfe111ef5e578cd3dc9f63 (libosmocore) Ie9e2add7bbfae651c04e230d62e37cebeb91b0f5 (libosmo-sccp) I26be5c4b06a680f25f19797407ab56a5a4880ddc (osmo-mgw) Ida0e59f9a1f2dd18efea0a51680a67b69f141efa (osmo-mgw) I9a3effd38e72841529df6c135c077116981dea36 (osmo-mgw) Change-Id: I27e4988e0371808b512c757d2b52ada1615067bd
Diffstat (limited to 'src/libmsc')
-rw-r--r--src/libmsc/Makefile.am38
-rw-r--r--src/libmsc/a_iface.c687
-rw-r--r--src/libmsc/a_iface_bssap.c730
-rw-r--r--src/libmsc/a_reset.c150
-rw-r--r--src/libmsc/call_leg.c348
-rw-r--r--src/libmsc/cell_id_list.c79
-rw-r--r--src/libmsc/e_link.c372
-rw-r--r--src/libmsc/gsm_04_08.c1354
-rw-r--r--src/libmsc/gsm_04_08_cc.c508
-rw-r--r--src/libmsc/gsm_04_11.c168
-rw-r--r--src/libmsc/gsm_04_11_gsup.c59
-rw-r--r--src/libmsc/gsm_04_14.c36
-rw-r--r--src/libmsc/gsm_04_80.c24
-rw-r--r--src/libmsc/gsm_09_11.c216
-rw-r--r--src/libmsc/gsm_subscriber.c216
-rw-r--r--src/libmsc/gsup_client_mux.c163
-rw-r--r--src/libmsc/iu_dummy.c99
-rw-r--r--src/libmsc/iucs.c249
-rw-r--r--src/libmsc/iucs_ranap.c137
-rw-r--r--src/libmsc/mncc.c100
-rw-r--r--src/libmsc/mncc_builtin.c6
-rw-r--r--src/libmsc/mncc_call.c760
-rw-r--r--src/libmsc/mncc_sock.c11
-rw-r--r--src/libmsc/msc_a.c1651
-rw-r--r--src/libmsc/msc_a_remote.c392
-rw-r--r--src/libmsc/msc_ho.c879
-rw-r--r--src/libmsc/msc_i.c383
-rw-r--r--src/libmsc/msc_i_remote.c245
-rw-r--r--src/libmsc/msc_ifaces.c143
-rw-r--r--src/libmsc/msc_mgcp.c1254
-rw-r--r--src/libmsc/msc_net_init.c126
-rw-r--r--src/libmsc/msc_t.c962
-rw-r--r--src/libmsc/msc_t_remote.c226
-rw-r--r--src/libmsc/msc_vty.c226
-rw-r--r--src/libmsc/msub.c590
-rw-r--r--src/libmsc/neighbor_ident.c191
-rw-r--r--src/libmsc/neighbor_ident_vty.c421
-rw-r--r--src/libmsc/osmo_msc.c227
-rw-r--r--src/libmsc/paging.c183
-rw-r--r--src/libmsc/ran_conn.c908
-rw-r--r--src/libmsc/ran_infra.c118
-rw-r--r--src/libmsc/ran_msg.c160
-rw-r--r--src/libmsc/ran_msg_a.c1284
-rw-r--r--src/libmsc/ran_msg_iu.c505
-rw-r--r--src/libmsc/ran_peer.c659
-rw-r--r--src/libmsc/ran_up_l2.c0
-rw-r--r--src/libmsc/rrlp.c22
-rw-r--r--src/libmsc/rtp_stream.c389
-rw-r--r--src/libmsc/sccp_ran.c216
-rw-r--r--src/libmsc/sgs_iface.c152
-rw-r--r--src/libmsc/sgs_server.c2
-rw-r--r--src/libmsc/silent_call.c193
-rw-r--r--src/libmsc/smpp_openbsc.c46
-rw-r--r--src/libmsc/smpp_smsc.h8
-rw-r--r--src/libmsc/sms_queue.c16
-rw-r--r--src/libmsc/transaction.c137
56 files changed, 12897 insertions, 6527 deletions
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index 454b9708e..d83489680 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -28,11 +28,12 @@ noinst_LIBRARIES = \
$(NULL)
libmsc_a_SOURCES = \
- a_iface.c \
- a_iface_bssap.c \
- a_reset.c \
+ call_leg.c \
+ cell_id_list.c \
+ sccp_ran.c \
msc_vty.c \
db.c \
+ e_link.c \
gsm_04_08.c \
gsm_04_08_cc.c \
gsm_04_11.c \
@@ -40,31 +41,42 @@ libmsc_a_SOURCES = \
gsm_04_14.c \
gsm_04_80.c \
gsm_09_11.c \
- gsm_subscriber.c \
+ gsup_client_mux.c \
mncc.c \
mncc_builtin.c \
mncc_sock.c \
- msc_ifaces.c \
- msc_mgcp.c \
+ mncc_call.c \
+ msub.c \
+ msc_a.c \
+ msc_a_remote.c \
+ msc_i.c \
+ msc_i_remote.c \
+ msc_t.c \
+ msc_t_remote.c \
+ msc_ho.c \
+ neighbor_ident.c \
+ neighbor_ident_vty.c \
+ paging.c \
ran_conn.c \
+ ran_infra.c \
+ ran_msg.c \
+ ran_msg_a.c \
+ ran_peer.c \
rrlp.c \
+ rtp_stream.c \
silent_call.c \
sms_queue.c \
transaction.c \
- osmo_msc.c \
+ msc_net_init.c \
ctrl_commands.c \
sgs_iface.c \
sgs_server.c \
sgs_vty.c \
$(NULL)
+
if BUILD_IU
libmsc_a_SOURCES += \
- iucs.c \
- iucs_ranap.c \
- $(NULL)
-else
-libmsc_a_SOURCES += \
- iu_dummy.c \
+ ran_msg_iu.c \
$(NULL)
endif
diff --git a/src/libmsc/a_iface.c b/src/libmsc/a_iface.c
deleted file mode 100644
index 91a2b6a3e..000000000
--- a/src/libmsc/a_iface.c
+++ /dev/null
@@ -1,687 +0,0 @@
-/* (C) 2017 by sysmocom s.f.m.c. GmbH
- * (C) 2018 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserved
- *
- * Author: Philipp Maier
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * 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/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 <osmocom/msc/debug.h>
-#include <osmocom/msc/msc_ifaces.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/a_iface_bssap.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/mgcp_client/mgcp_client.h>
-#include <osmocom/core/byteswap.h>
-#include <osmocom/sccp/sccp_types.h>
-#include <osmocom/msc/a_reset.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/ran_conn.h>
-
-#include <errno.h>
-
-#define LOGPCONN LOG_RAN_CONN
-
-/* 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 */
- struct bsc_context *bsc;
-};
-
-/* 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, struct bsc_context *bsc, uint32_t conn_id)
-{
- struct bsc_conn *conn;
-
- conn = talloc_zero(ctx, struct bsc_conn);
- OSMO_ASSERT(conn);
-
- conn->conn_id = conn_id;
- conn->bsc = bsc;
-
- 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;
-
- llist_for_each_entry_safe(conn, conn_temp, &active_connections, list) {
- if (conn->conn_id == conn_id) {
- LOGP(DBSSAP, LOGL_DEBUG, "(conn%u) Removing A-interface conn\n", conn->conn_id);
- llist_del(&conn->list);
- talloc_free(conn);
- }
- }
-}
-
-/* Find a specified connection id */
-static struct bsc_conn *find_bsc_con(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 conn;
- }
- }
-
- return NULL;
-}
-
-/* Check if a specified connection id has an active SCCP connection */
-static bool check_connection_active(uint32_t conn_id)
-{
- if (find_bsc_con(conn_id))
- return true;
- else
- return false;
-}
-
-/* Get the context for a specific calling (BSC) address */
-static struct bsc_context *get_bsc_context_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;
- }
-
- ss7 = osmo_ss7_instance_find(gsm_network->a.cs7_instance);
- OSMO_ASSERT(ss7);
- LOGP(DBSSAP, LOGL_NOTICE, "The calling BSC (%s) is unknown to this MSC ...\n",
- osmo_sccp_addr_name(ss7, addr));
- return NULL;
-}
-
-/* wrapper around osmo_sccp_tx_data_msg(): Transmit a fully encoded BSSAP (DTAP or BSSMAP) message */
-static int a_iface_tx_bssap(const struct ran_conn *conn, struct msgb *msg)
-{
- OSMO_ASSERT(conn);
- OSMO_ASSERT(conn->a.scu);
-
- LOGPCONN(conn, LOGL_DEBUG, "N-DATA.req(%s)\n", msgb_hexdump_l3(msg));
-
- /* some consistency checks to ensure we don't send invalid length */
- switch (msg->l3h[0]) {
- case BSSAP_MSG_DTAP:
- OSMO_ASSERT(msgb_l3len(msg) == msg->l3h[2] + 3);
- break;
- case BSSAP_MSG_BSS_MANAGEMENT:
- OSMO_ASSERT(msgb_l3len(msg) == msg->l3h[1] + 2);
- break;
- default:
- break;
- }
-
- return osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg);
-}
-
-/* Send DTAP message via A-interface, take ownership of msg */
-int a_iface_tx_dtap(struct msgb *msg)
-{
- const struct ran_conn *conn;
- struct msgb *msg_resp;
-
- uint8_t link_id = OMSC_LINKID_CB(msg);
- OSMO_ASSERT(msg);
- conn = (struct ran_conn *)msg->dst;
- OSMO_ASSERT(conn);
-
- LOGPCONN(conn, LOGL_DEBUG, "Passing DTAP message (DLCI=0x%02x) from MSC to BSC\n", link_id);
-
- msg->l3h = msg->data;
- msg_resp = gsm0808_create_dtap(msg, link_id);
-
- /* gsm0808_create_dtap() has copied the data to msg_resp,
- * so msg has served its purpose now */
- msgb_free(msg);
-
- if (!msg_resp) {
- LOGPCONN(conn, LOGL_ERROR, "Unable to generate BSSMAP DTAP message!\n");
- return -EINVAL;
- }
-
- /* osmo_sccp_tx_data_msg() takes ownership of msg_resp */
- return a_iface_tx_bssap(conn, msg_resp);
-}
-
-/* Send Cipher mode command via A-interface */
-int a_iface_tx_cipher_mode(const struct ran_conn *conn,
- struct gsm0808_encrypt_info *ei, int include_imeisv)
-{
- /* TODO generalize for A- and Iu interfaces, don't name after 08.08 */
- struct msgb *msg_resp;
- uint8_t crm = 0x01;
-
- OSMO_ASSERT(conn);
- LOGPCONN(conn, LOGL_DEBUG, "Tx BSSMAP CIPHER MODE COMMAND to BSC, %u ciphers (%s)",
- ei->perm_algo_len, osmo_hexdump_nospc(ei->perm_algo, ei->perm_algo_len));
- LOGPC(DBSSAP, LOGL_DEBUG, " key %s\n", osmo_hexdump_nospc(ei->key, ei->key_len));
-
- msg_resp = gsm0808_create_cipher(ei, include_imeisv ? &crm : NULL);
- return a_iface_tx_bssap(conn, 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_list2 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[0].lac = 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_fsm)) {
- LOGP(DBSSAP, LOGL_DEBUG,
- "Tx BSSMAP 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_paging2(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(DBSSAP, 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(DBSSAP, 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_speech_version_l3_to_A(int speech_ver)
-{
- /* The speech versions that are transmitted in the Bearer capability
- * information element, that is transmitted on the Layer 3 (CC)
- * 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;
- case GSM48_BCAP_SV_HR:
- return GSM0808_PERM_HR1;
- case GSM48_BCAP_SV_EFR:
- return GSM0808_PERM_FR2;
- case GSM48_BCAP_SV_AMR_F:
- return GSM0808_PERM_FR3;
- case GSM48_BCAP_SV_AMR_H:
- return GSM0808_PERM_HR3;
- case GSM48_BCAP_SV_AMR_OFW:
- return GSM0808_PERM_FR4;
- case GSM48_BCAP_SV_AMR_OHW:
- return GSM0808_PERM_HR4;
- case GSM48_BCAP_SV_AMR_FW:
- return GSM0808_PERM_FR5;
- case GSM48_BCAP_SV_AMR_OH:
- return GSM0808_PERM_HR6;
- }
-
- /* If nothing matches, tag the result as invalid */
- LOGP(DBSSAP, LOGL_ERROR, "Invalid permitted speech version: %d\n", speech_ver);
- return 0xFF;
-}
-
-/* Convert speech preference field */
-static uint8_t convert_speech_pref_l3_to_A(int radio)
-{
- /* The Radio channel requirement field that is transmitted in the
- * Bearer capability information element, that is transmitted on the
- * Layer 3 (CC) 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(DBSSAP, LOGL_ERROR, "Invalid radio channel preference: %d; defaulting to full rate.\n",
- radio);
- 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_speech_version_l3_to_A(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_speech_pref_l3_to_A(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)
-{
- const struct ran_conn *conn;
- struct gsm0808_channel_type ct;
- struct gsm0808_speech_codec_list scl;
- 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);
-
- LOGPCONN(conn, LOGL_DEBUG, "Tx BSSMAP ASSIGNMENT COMMAND to BSC\n");
-
- /* Channel type */
- rc = enc_channel_type(&ct, &trans->bearer_cap);
- if (rc < 0) {
- LOGPCONN(conn, LOGL_ERROR, "Not sending Assignment to BSC: failed to generate channel type\n");
- return -EINVAL;
- }
-
- /* Speech codec list */
- rc = enc_speech_codec_list(&scl, &ct);
- if (rc < 0) {
- LOGPCONN(conn, LOGL_ERROR, "Not sending Assignment to BSC: failed to generate speech codec list\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.local_port_ran);
- rtp_addr_in.sin_addr.s_addr = inet_addr(conn->rtp.local_addr_ran);
-
- if (rtp_addr_in.sin_addr.s_addr == INADDR_NONE) {
- LOGPCONN(conn, LOGL_ERROR, "Invalid RTP-Address -- assignment not sent!\n");
- return -EINVAL;
- }
- if (rtp_addr_in.sin_port == 0) {
- LOGPCONN(conn, LOGL_ERROR, "Invalid RTP-Port -- assignment not sent!\n");
- return -EINVAL;
- }
-
- 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, NULL);
- return a_iface_tx_bssap(conn, msg);
-}
-
-/* Send clear command via A-interface */
-int a_iface_tx_clear_cmd(const struct ran_conn *conn)
-{
- struct msgb *msg;
- struct vlr_subscr *vsub = conn->vsub;
- bool csfb_ind = false;
-
- LOGPCONN(conn, LOGL_INFO, "Tx BSSMAP CLEAR COMMAND to BSC\n");
-
- if (vsub && vsub->sgs_fsm->state == SGS_UE_ST_ASSOCIATED)
- csfb_ind = true;
-
- msg = gsm0808_create_clear_command2(GSM0808_CAUSE_CALL_CONTROL, csfb_ind);
- return a_iface_tx_bssap(conn, msg);
-}
-
-int a_iface_tx_classmark_request(const struct ran_conn *conn)
-{
- struct msgb *msg;
-
- LOGPCONN(conn, LOGL_INFO, "Tx BSSMAP CLASSMARK REQUEST to BSC\n");
-
- msg = gsm0808_create_classmark_request();
- return a_iface_tx_bssap(conn, 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 RAN 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(DBSSAP, LOGL_NOTICE, "Tx BSSMAP 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 struct bsc_context *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;
-
- ss7 = osmo_ss7_instance_find(gsm_network->a.cs7_instance);
- OSMO_ASSERT(ss7);
- LOGP(DBSSAP, 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);
-
- return bsc_ctx;
-}
-
-/* start the BSSMAP RESET fsm */
-void a_start_reset(struct bsc_context *bsc_ctx, bool already_connected)
-{
- char bsc_name[32];
- OSMO_ASSERT(bsc_ctx->reset_fsm == NULL);
- /* Start reset procedure to make the new connection active */
- snprintf(bsc_name, sizeof(bsc_name), "bsc-%i", bsc_ctx->bsc_addr.pc);
- bsc_ctx->reset_fsm = a_reset_alloc(bsc_ctx, bsc_name, a_reset_cb, bsc_ctx, already_connected);
-}
-
-/* determine if given msg is BSSMAP RESET related (true) or not (false) */
-static bool bssmap_is_reset(struct msgb *msg)
-{
- struct bssmap_header *bs = (struct bssmap_header *)msgb_l2(msg);
-
- if (msgb_l2len(msg) < sizeof(*bs))
- return false;
-
- if (bs->type != BSSAP_MSG_BSS_MANAGEMENT)
- return false;
-
- if (msg->l2h[sizeof(*bs)] == BSS_MAP_MSG_RESET)
- return true;
-
- if (msg->l2h[sizeof(*bs)] == BSS_MAP_MSG_RESET_ACKNOWLEDGE)
- return true;
-
- return false;
-}
-
-/* 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;
- struct bsc_conn *bsc_con;
-
- memset(&a_conn_info, 0, sizeof(a_conn_info));
- a_conn_info.network = gsm_network;
-
- switch (OSMO_PRIM_HDR(&scu_prim->oph)) {
- case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
- /* Handle inbound connection indication */
- a_conn_info.conn_id = scu_prim->u.connect.conn_id;
- a_conn_info.bsc = get_bsc_context_by_sccp_addr(&scu_prim->u.unitdata.calling_addr);
- if (!a_conn_info.bsc) {
- /* We haven't heard from this BSC before, allocate it */
- a_conn_info.bsc = add_bsc(&scu_prim->u.connect.called_addr,
- &scu_prim->u.connect.calling_addr, scu);
- a_start_reset(a_conn_info.bsc, false);
- } else {
- /* This BSC is already known to us, check if we have been through reset yet */
- if (a_reset_conn_ready(a_conn_info.bsc->reset_fsm) == false) {
- LOGP(DBSSAP, LOGL_NOTICE, "Refusing N-CONNECT.ind(%u, %s), BSC not reset yet\n",
- scu_prim->u.connect.conn_id, msgb_hexdump_l2(oph->msg));
- rc = osmo_sccp_tx_disconn(scu, a_conn_info.conn_id, &a_conn_info.bsc->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(DBSSAP, LOGL_DEBUG, "N-CONNECT.ind(%u, %s)\n",
- scu_prim->u.connect.conn_id, msgb_hexdump_l2(oph->msg));
- rc = a_sccp_rx_dt(scu, &a_conn_info, oph->msg);
- } else {
- LOGP(DBSSAP, LOGL_DEBUG, "N-CONNECT.ind(%u)\n", scu_prim->u.connect.conn_id);
- rc = -ENODATA;
- }
-
- if (rc < 0) {
- /* initial message (COMPL L3) caused some error, we didn't allocate
- * a subscriber_conn and must close the connection again */
- rc = osmo_sccp_tx_disconn(scu, a_conn_info.conn_id,
- &a_conn_info.bsc->msc_addr,
- SCCP_RETURN_CAUSE_UNQUALIFIED);
- } else
- record_bsc_con(scu, a_conn_info.bsc, scu_prim->u.connect.conn_id);
- }
- break;
-
- case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
- /* Handle incoming connection oriented data */
- bsc_con = find_bsc_con(scu_prim->u.data.conn_id);
- if (!bsc_con) {
- LOGP(DBSSAP, LOGL_ERROR, "N-DATA.ind(%u, %s) for unknown conn_id\n",
- scu_prim->u.data.conn_id, msgb_hexdump_l2(oph->msg));
- break;
- }
- a_conn_info.conn_id = scu_prim->u.data.conn_id;
- a_conn_info.bsc = bsc_con->bsc;
- LOGP(DBSSAP, LOGL_DEBUG, "N-DATA.ind(%u, %s)\n",
- scu_prim->u.data.conn_id, msgb_hexdump_l2(oph->msg));
- a_sccp_rx_dt(scu, &a_conn_info, oph->msg);
- break;
-
- case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
- /* Handle inbound UNITDATA */
-
- /* Get BSC context, create a new one if necessary */
- a_conn_info.bsc = get_bsc_context_by_sccp_addr(&scu_prim->u.unitdata.calling_addr);
- if (!a_conn_info.bsc) {
- /* We haven't heard from this BSC before, allocate it */
- a_conn_info.bsc = add_bsc(&scu_prim->u.unitdata.called_addr,
- &scu_prim->u.unitdata.calling_addr, scu);
- /* Make sure that reset procedure is started */
- a_start_reset(a_conn_info.bsc, false);
- }
-
- /* As long as we are in the reset phase, only reset related BSSMAP messages may pass
- * beond here. */
- if (!bssmap_is_reset(oph->msg) && a_reset_conn_ready(a_conn_info.bsc->reset_fsm) == false) {
- LOGP(DBSSAP, LOGL_NOTICE, "Ignoring N-UNITDATA.ind(%s), BSC not reset yet\n",
- msgb_hexdump_l2(oph->msg));
- break;
- }
-
- DEBUGP(DBSSAP, "N-UNITDATA.ind(%s)\n", msgb_hexdump_l2(oph->msg));
- a_sccp_rx_udt(scu, &a_conn_info, oph->msg);
- break;
-
- default:
- LOGP(DBSSAP, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n",
- get_value_string(osmo_prim_op_names, oph->operation), oph->primitive);
- break;
- }
-
- /* We didn't transfer msgb ownership to any downstream functions so we rely on
- * this single/central location to free() the msgb wrapping the primitive */
- msgb_free(oph->msg);
- return rc;
-}
-
-/* Clear all RAN connections on a specified BSC */
-void a_clear_all(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *bsc_addr)
-{
- struct ran_conn *conn;
- struct ran_conn *conn_temp;
- struct gsm_network *network = gsm_network;
-
- OSMO_ASSERT(scu);
- OSMO_ASSERT(bsc_addr);
-
- llist_for_each_entry_safe(conn, conn_temp, &network->ran_conns, entry) {
- /* Clear only A connections and connections that actually
- * belong to the specified BSC */
- if (conn->via_ran == OSMO_RAT_GERAN_A && memcmp(bsc_addr, &conn->a.bsc_addr, sizeof(conn->a.bsc_addr)) == 0) {
- uint32_t conn_id = conn->a.conn_id;
- LOGPCONN(conn, LOGL_NOTICE, "Dropping orphaned RAN connection\n");
- /* This call will/may talloc_free(conn), so we must save conn_id above */
- ran_conn_clear_request(conn, GSM48_CC_CAUSE_SWITCH_CONG);
-
- /* If there is still an SCCP connection active, remove it now */
- if (check_connection_active(conn_id)) {
- osmo_sccp_tx_disconn(scu, conn_id, bsc_addr,
- SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
- a_delete_bsc_con(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(DBSSAP, 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
deleted file mode 100644
index cb245b805..000000000
--- a/src/libmsc/a_iface_bssap.c
+++ /dev/null
@@ -1,730 +0,0 @@
-/* (C) 2017 by Sysmocom s.f.m.c. GmbH
- * (C) 2018 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserved
- *
- * Author: Philipp Maier
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * 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/gsm48.h>
-#include <osmocom/gsm/gsm0808_utils.h>
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/gsm_data.h>
-#include <osmocom/msc/a_iface_bssap.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/core/byteswap.h>
-#include <osmocom/msc/a_reset.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/msc/msc_mgcp.h>
-#include <osmocom/msc/ran_conn.h>
-
-#include <errno.h>
-
-#define IP_V4_ADDR_LEN 4
-
-#define LOGPCONN LOG_RAN_CONN
-
-/*
- * Helper functions to lookup and allocate subscribers
- */
-
-/* Allocate a new RAN connection */
-static struct ran_conn *ran_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 ran_conn *conn;
-
- LOGP(DMSC, LOGL_DEBUG, "Allocating A-Interface RAN conn: lac %i, conn_id %i\n", lac, conn_id);
-
- conn = ran_conn_alloc(network, OSMO_RAT_GERAN_A, lac);
- if (!conn)
- return NULL;
-
- 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 RAN connection */
- memcpy(&conn->a.bsc_addr, &a_conn_info->bsc->bsc_addr, sizeof(conn->a.bsc_addr));
-
- LOGPCONN(conn, LOGL_DEBUG, "A-Interface RAN connection successfully allocated!\n");
- return conn;
-}
-
-/* Return an existing A RAN connection record for the given
- * connection IDs, or return NULL if not found. */
-static struct ran_conn *ran_conn_lookup_a(const struct gsm_network *network, int conn_id)
-{
- struct ran_conn *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->ran_conns, entry) {
- if (conn->via_ran == OSMO_RAT_GERAN_A && conn->a.conn_id == conn_id) {
- LOGPCONN(conn, LOGL_DEBUG, "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(DBSSAP, LOGL_NOTICE, "Rx BSSMAP RESET from BSC %s, sending RESET ACK\n",
- osmo_sccp_addr_name(ss7, &a_conn_info->bsc->bsc_addr));
- osmo_sccp_tx_unitdata_msg(scu, &a_conn_info->bsc->msc_addr, &a_conn_info->bsc->bsc_addr,
- gsm0808_create_reset_ack());
-
- /* Make sure all orphand RAN connections will be cleard */
- a_clear_all(scu, &a_conn_info->bsc->bsc_addr);
-
- if (!a_conn_info->bsc->reset_fsm)
- a_start_reset(a_conn_info->bsc, true);
-
- /* Treat an incoming RESET like an ACK to any RESET request we may have just sent.
- * After all, what we wanted is the A interface to be reset, which we now know has happened. */
- a_reset_ack_confirm(a_conn_info->bsc->reset_fsm);
-}
-
-/* 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->bsc->reset_fsm == NULL) {
- LOGP(DBSSAP, LOGL_ERROR, "Received RESET ACK from an unknown BSC %s, ignoring...\n",
- osmo_sccp_addr_name(ss7, &a_conn_info->bsc->bsc_addr));
- return;
- }
-
- LOGP(DBSSAP, LOGL_NOTICE, "Received RESET ACK from BSC %s\n",
- osmo_sccp_addr_name(ss7, &a_conn_info->bsc->bsc_addr));
-
- /* Confirm that we managed to get the reset ack message
- * towards the connection reset logic */
- a_reset_ack_confirm(a_conn_info->bsc->reset_fsm);
-}
-
-/* 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(DBSSAP, LOGL_NOTICE, "Error: No data received -- discarding message!\n");
- return;
- }
-
- LOGP(DBSSAP, LOGL_DEBUG, "Rx BSSMAP UDT %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(DBSSAP, LOGL_NOTICE, "Unimplemented message format: %s -- message discarded!\n",
- gsm0808_bssmap_name(msg->l3h[0]));
- }
-}
-
-/* Receive incoming connection less data messages via sccp */
-void a_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(DBSSAP, LOGL_DEBUG, "Rx BSSMAP UDT: %s\n", msgb_hexdump_l2(msg));
-
- if (msgb_l2len(msg) < sizeof(*bs)) {
- LOGP(DBSSAP, LOGL_ERROR, "Error: Header is too short -- discarding message!\n");
- return;
- }
-
- bs = (struct bssmap_header *)msgb_l2(msg);
- if (bs->length < msgb_l2len(msg) - sizeof(*bs)) {
- LOGP(DBSSAP, LOGL_ERROR, "Error: Message is too short -- discarding message!\n");
- 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(DBSSAP, LOGL_ERROR,
- "Error: Unimplemented message type: %s -- message discarded!\n", gsm0808_bssmap_name(bs->type));
- }
-}
-
-/*
- * BSSMAP handling for connection oriented data
- */
-
-/* Endpoint to handle BSSMAP clear request */
-static int bssmap_rx_clear_rqst(struct ran_conn *conn,
- struct msgb *msg, struct tlv_parsed *tp)
-{
- uint8_t cause;
-
- LOGPCONN(conn, LOGL_INFO, "Rx BSSMAP CLEAR REQUEST\n");
-
- if (!TLVP_PRESENT(tp, GSM0808_IE_CAUSE)) {
- LOGP(DBSSAP, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
- return -EINVAL;
- }
- cause = TLVP_VAL(tp, GSM0808_IE_CAUSE)[0];
-
- ran_conn_mo_close(conn, cause);
-
- return 0;
-}
-
-/* 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 ran_conn *conn)
-{
- int rc;
-
- LOGPCONN(conn, LOGL_INFO, "Rx BSSMAP CLEAR COMPLETE, releasing SCCP connection\n");
-
- if (conn)
- ran_conn_rx_bssmap_clear_complete(conn);
-
- rc = osmo_sccp_tx_disconn(scu, a_conn_info->conn_id,
- NULL, SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
-
- /* Remove the record from the list with active connections. */
- a_delete_bsc_con(a_conn_info->conn_id);
-
- 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 gsm0808_cell_id_list2 cil;
- uint16_t lac = 0;
- uint8_t data_length;
- const uint8_t *data;
- struct gsm_network *network = a_conn_info->network;
- struct ran_conn *conn;
-
- LOGP(DBSSAP, LOGL_INFO, "Rx BSSMAP COMPLETE L3 INFO (conn_id=%i)\n", a_conn_info->conn_id);
-
- if (!TLVP_PRESENT(tp, GSM0808_IE_CELL_IDENTIFIER)) {
- LOGP(DBSSAP, LOGL_ERROR, "Mandatory CELL IDENTIFIER not present -- discarding message!\n");
- return -EINVAL;
- }
- if (!TLVP_PRESENT(tp, GSM0808_IE_LAYER_3_INFORMATION)) {
- LOGP(DBSSAP, LOGL_ERROR, "Mandatory LAYER 3 INFORMATION not present -- discarding message!\n");
- return -EINVAL;
- }
-
- /* Parse Cell ID element -- this should yield a cell identifier "list" with 1 element. */
-
- data_length = TLVP_LEN(tp, GSM0808_IE_CELL_IDENTIFIER);
- data = TLVP_VAL(tp, GSM0808_IE_CELL_IDENTIFIER);
- if (gsm0808_dec_cell_id_list2(&cil, data, data_length) < 0 || cil.id_list_len != 1) {
- LOGP(DBSSAP, LOGL_ERROR,
- "Unable to parse element CELL IDENTIFIER -- discarding message!\n");
- return -EINVAL;
- }
-
- /* Determine the LAC which we will use for this subscriber. */
- switch (cil.id_discr) {
- case CELL_IDENT_WHOLE_GLOBAL: {
- const struct osmo_cell_global_id *id = &cil.id_list[0].global;
- if (osmo_plmn_cmp(&id->lai.plmn, &network->plmn) != 0) {
- LOGP(DBSSAP, LOGL_ERROR,
- "WHOLE GLOBAL CELL IDENTIFIER does not match network MCC/MNC -- discarding message!\n");
- return -EINVAL;
- }
- lac = id->lai.lac;
- break;
- }
- case CELL_IDENT_LAC_AND_CI: {
- const struct osmo_lac_and_ci_id *id = &cil.id_list[0].lac_and_ci;
- lac = id->lac;
- break;
- }
- case CELL_IDENT_LAI_AND_LAC: {
- const struct osmo_location_area_id *id = &cil.id_list[0].lai_and_lac;
- if (osmo_plmn_cmp(&id->plmn, &network->plmn) != 0) {
- LOGP(DBSSAP, LOGL_ERROR,
- "LAI AND LAC CELL IDENTIFIER does not match network MCC/MNC -- discarding message!\n");
- return -EINVAL;
- }
- lac = id->lac;
- break;
- }
- case CELL_IDENT_LAC:
- lac = cil.id_list[0].lac;
- break;
-
- case CELL_IDENT_CI:
- case CELL_IDENT_NO_CELL:
- case CELL_IDENT_BSS:
- LOGP(DBSSAP, LOGL_ERROR,
- "CELL IDENTIFIER does not specify a LAC -- discarding message!\n");
- return -EINVAL;
-
- default:
- LOGP(DBSSAP, LOGL_ERROR,
- "Unable to parse element CELL IDENTIFIER (unknown cell identification discriminator 0x%x) "
- "-- discarding message!\n", cil.id_discr);
- return -EINVAL;
- }
-
- /* Parse Layer 3 Information element */
- msg->l3h = (uint8_t*)TLVP_VAL(tp, GSM0808_IE_LAYER_3_INFORMATION);
- msgb_l3trim(msg, TLVP_LEN(tp, GSM0808_IE_LAYER_3_INFORMATION));
-
- if (msgb_l3len(msg) < sizeof(struct gsm48_hdr)) {
- LOGP(DBSSAP, LOGL_ERROR, "COMPL_L3 with too short L3 (%d) -- discarding\n",
- msgb_l3len(msg));
- return -ENODATA;
- }
-
- /* Create new subscriber context */
- conn = ran_conn_allocate_a(a_conn_info, network, lac, scu, a_conn_info->conn_id);
-
- /* Handover location update to the MSC code */
- ran_conn_compl_l3(conn, msg, 0);
- return 0;
-}
-
-/* Endpoint to handle BSSMAP classmark update */
-static int bssmap_rx_classmark_upd(struct ran_conn *conn, struct msgb *msg,
- struct tlv_parsed *tp)
-{
- const uint8_t *cm2 = NULL;
- const uint8_t *cm3 = NULL;
- uint8_t cm2_len = 0;
- uint8_t cm3_len = 0;
-
- LOGPCONN(conn, LOGL_DEBUG, "Rx BSSMAP CLASSMARK UPDATE\n");
-
- if (!TLVP_PRESENT(tp, GSM0808_IE_CLASSMARK_INFORMATION_T2)) {
- LOGPCONN(conn, LOGL_ERROR, "Mandatory Classmark Information Type 2 not present -- discarding message!\n");
- return -EINVAL;
- }
-
- 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 */
- ran_conn_classmark_chg(conn, cm2, cm2_len, cm3, cm3_len);
-
- return 0;
-}
-
-/* Endpoint to handle BSSMAP cipher mode complete */
-static int bssmap_rx_ciph_compl(struct ran_conn *conn, struct msgb *msg,
- struct tlv_parsed *tp)
-{
- /* 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 ran_conn_cipher_mode_compl()
- * is not able to deal with msg = NULL and apperently
- * ran_conn_cipher_mode_compl() was never meant to be used without L3 data.
- * This needs to be discussed further! */
-
- uint8_t alg_id = 1;
- struct rate_ctr_group *msc = conn->network->msc_ctrs;
-
- LOGPCONN(conn, LOGL_DEBUG, "Rx BSSMAP CIPHER MODE COMPLETE\n");
-
- 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 = (uint8_t*)TLVP_VAL(tp, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS);
- msgb_l3trim(msg, TLVP_LEN(tp, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS));
- } else {
- msg = NULL;
- }
-
- rate_ctr_inc(&msc->ctr[MSC_CTR_BSSMAP_CIPHER_MODE_COMPLETE]);
-
- /* Hand over cipher mode complete message to the MSC */
- ran_conn_cipher_mode_compl(conn, msg, alg_id);
-
- return 0;
-}
-
-/* Endpoint to handle BSSMAP cipher mode reject, 3GPP TS 08.08 §3.2.1.48 */
-static int bssmap_rx_ciph_rej(struct ran_conn *conn,
- struct msgb *msg, struct tlv_parsed *tp)
-{
- int rc;
- enum gsm0808_cause cause;
- struct rate_ctr_group *msc = conn->network->msc_ctrs;
-
- LOGPCONN(conn, LOGL_NOTICE, "RX BSSMAP CIPHER MODE REJECT\n");
-
- rc = gsm0808_get_cipher_reject_cause(tp);
- if (rc < 0) {
- LOGPCONN(conn, LOGL_ERROR, "failed (%s) to extract Cause from Cipher mode reject: %s\n",
- strerror(-rc), msgb_hexdump(msg));
- return rc;
- }
-
- rate_ctr_inc(&msc->ctr[MSC_CTR_BSSMAP_CIPHER_MODE_REJECT]);
- cause = (enum gsm0808_cause)rc;
- LOGPCONN(conn, LOGL_NOTICE, "Cipher mode rejection cause: %s\n", gsm0808_cause_name(cause));
-
- /* FIXME: Can we do something meaningful here? e.g. report to the
- * msc code somehow that the cipher mode command has failed. */
-
- return 0;
-}
-
-/* Endpoint to handle BSSMAP assignment failure */
-static int bssmap_rx_ass_fail(struct ran_conn *conn, struct msgb *msg,
- struct tlv_parsed *tp)
-{
- uint8_t cause;
- uint8_t *rr_cause_ptr = NULL;
- uint8_t rr_cause;
-
- LOGPCONN(conn, LOGL_NOTICE, "Rx BSSMAP ASSIGNMENT FAILURE message\n");
-
- if (!TLVP_PRESENT(tp, GSM0808_IE_CAUSE)) {
- LOGPCONN(conn, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
- return -EINVAL;
- }
- 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 ran_conn_assign_fail() function
- * call has to change. However ran_conn_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 */
- ran_conn_assign_fail(conn, cause, rr_cause_ptr);
-
- return 0;
-}
-
-/* Endpoint to handle sapi "n" reject */
-static int bssmap_rx_sapi_n_rej(struct ran_conn *conn, struct msgb *msg,
- struct tlv_parsed *tp)
-{
- uint8_t dlci;
-
- LOGPCONN(conn, LOGL_NOTICE, "Rx BSSMAP SAPI-N-REJECT message\n");
-
- /* 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 */
- if (!TLVP_PRESENT(tp, GSM0808_IE_CAUSE)) {
- LOGPCONN(conn, LOGL_ERROR, "Cause code is missing -- discarding message!\n");
- return -EINVAL;
- }
- if (!TLVP_PRESENT(tp, GSM0808_IE_DLCI)) {
- LOGPCONN(conn, LOGL_ERROR, "DLCI is missing -- discarding message!\n");
- return -EINVAL;
- }
- dlci = TLVP_VAL(tp, GSM0808_IE_DLCI)[0];
-
- /* Inform the MSC about the sapi "n" reject event */
- ran_conn_sapi_n_reject(conn, dlci);
-
- return 0;
-}
-
-/* Use the speech codec info we go with the assignment complete to dtermine
- * which codec we will signal to the MGW */
-static enum mgcp_codecs mgcp_codec_from_sc(struct gsm0808_speech_codec *sc)
-{
- switch (sc->type) {
- case GSM0808_SCT_FR1:
- return CODEC_GSM_8000_1;
- break;
- case GSM0808_SCT_FR2:
- return CODEC_GSMEFR_8000_1;
- break;
- case GSM0808_SCT_FR3:
- return CODEC_AMR_8000_1;
- break;
- case GSM0808_SCT_FR4:
- return CODEC_AMRWB_16000_1;
- break;
- case GSM0808_SCT_FR5:
- return CODEC_AMRWB_16000_1;
- break;
- case GSM0808_SCT_HR1:
- return CODEC_GSMHR_8000_1;
- break;
- case GSM0808_SCT_HR3:
- return CODEC_AMR_8000_1;
- break;
- case GSM0808_SCT_HR4:
- return CODEC_AMRWB_16000_1;
- break;
- case GSM0808_SCT_HR6:
- return CODEC_AMRWB_16000_1;
- break;
- default:
- return CODEC_PCMU_8000_1;
- break;
- }
-}
-
-/* Endpoint to handle assignment complete */
-static int bssmap_rx_ass_compl(struct ran_conn *conn, struct msgb *msg,
- struct tlv_parsed *tp)
-{
- struct sockaddr_storage rtp_addr;
- struct gsm0808_speech_codec sc;
- struct sockaddr_in *rtp_addr_in;
- int rc;
-
- LOGPCONN(conn, LOGL_INFO, "Rx BSSMAP ASSIGNMENT COMPLETE message\n");
-
- if (!TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
- LOGPCONN(conn, LOGL_ERROR, "AoIP transport identifier missing -- discarding message!\n");
- return -EINVAL;
- }
-
- /* 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) {
- LOGPCONN(conn, LOGL_ERROR, "Unable to decode aoip transport address.\n");
- return -EINVAL;
- }
-
- /* Decode speech codec (choosen) element */
- rc = gsm0808_dec_speech_codec(&sc, TLVP_VAL(tp, GSM0808_IE_SPEECH_CODEC),
- TLVP_LEN(tp, GSM0808_IE_SPEECH_CODEC));
- if (rc < 0) {
- LOGPCONN(conn, LOGL_ERROR, "Unable to decode speech codec (choosen).\n");
- return -EINVAL;
- }
- conn->rtp.codec_ran = mgcp_codec_from_sc(&sc);
-
- /* 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;
- msc_mgcp_ass_complete(conn, osmo_ntohs(rtp_addr_in->sin_port), inet_ntoa(rtp_addr_in->sin_addr));
- } else {
- LOGPCONN(conn, LOGL_ERROR, "Unsopported addressing scheme. (supports only IPV4)\n");
- return -EINVAL;
- }
-
- return 0;
-}
-
-/* Handle incoming LCLS-NOTIFICATION BSSMAP message: 3GPP TS 48.008 §3.2.1.93 */
-static int bssmap_rx_lcls_notif(const struct ran_conn *conn, const struct msgb *msg, const struct tlv_parsed *tp)
-{
-
- bool status_avail = TLVP_PRESENT(tp, GSM0808_IE_LCLS_BSS_STATUS),
- break_avail = TLVP_PRESENT(tp, GSM0808_IE_LCLS_BREAK_REQ);
-
- /* Either §3.2.2.119 LCLS-BSS-Status or §3.2.2.120 LCLS-Break-Request shall be present */
- if (!(status_avail ^ break_avail)) {
- LOGPCONN(conn, LOGL_ERROR, "Ignoring broken LCLS Notification message\n");
- return -EINVAL;
- }
-
- if (status_avail)
- LOGPCONN(conn, LOGL_NOTICE, "Received LCLS Status: %s\n",
- gsm0808_lcls_status_name(tlvp_val8(tp, GSM0808_IE_LCLS_BSS_STATUS, GSM0808_LCLS_STS_NA)));
-
- if (break_avail)
- LOGPCONN(conn, LOGL_NOTICE, "Received LCLS Break Request\n");
-
- return 0;
-}
-
-/* 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)
-{
- struct ran_conn *conn;
- struct tlv_parsed tp;
- int rc;
- uint8_t msg_type;
-
- if (msgb_l3len(msg) < 1) {
- LOGP(DBSSAP, LOGL_NOTICE, "Error: No data received -- discarding message!\n");
- return -1;
- }
- msg_type = msg->l3h[0];
-
- rc = osmo_bssap_tlv_parse(&tp, msg->l3h + 1, msgb_l3len(msg) - 1);
- if (rc < 0) {
- LOGP(DBSSAP, LOGL_ERROR, "Failed parsing TLV -- discarding message! %s\n",
- osmo_hexdump(msg->l3h, msgb_l3len(msg)));
- return -EINVAL;
- }
-
- /* Only message types allowed without a 'conn' */
- switch (msg_type) {
- case BSS_MAP_MSG_COMPLETE_LAYER_3:
- return bssmap_rx_l3_compl(scu, a_conn_info, msg, &tp);
- default:
- break;
- }
-
- conn = ran_conn_lookup_a(a_conn_info->network, a_conn_info->conn_id);
- if (!conn) {
- LOGP(DBSSAP, LOGL_ERROR, "Couldn't find ran_conn for conn_id=%d\n", a_conn_info->conn_id);
- /* We expect a Clear Complete to come in on a valid conn. But if for some reason we still
- * have the SCCP connection while the RAN connection data is already gone, at
- * least close the SCCP conn. */
-
- if (msg_type == BSS_MAP_MSG_CLEAR_COMPLETE)
- return bssmap_rx_clear_complete(scu, a_conn_info, NULL);
-
- return -EINVAL;
- }
-
- LOGPCONN(conn, LOGL_DEBUG, "Rx BSSMAP DT1 %s\n", gsm0808_bssmap_name(msg_type));
-
- switch (msg_type) {
- case BSS_MAP_MSG_CLEAR_RQST:
- return bssmap_rx_clear_rqst(conn, msg, &tp);
- case BSS_MAP_MSG_CLEAR_COMPLETE:
- return bssmap_rx_clear_complete(scu, a_conn_info, conn);
- case BSS_MAP_MSG_CLASSMARK_UPDATE:
- return bssmap_rx_classmark_upd(conn, msg, &tp);
- case BSS_MAP_MSG_CIPHER_MODE_COMPLETE:
- return bssmap_rx_ciph_compl(conn, msg, &tp);
- case BSS_MAP_MSG_CIPHER_MODE_REJECT:
- return bssmap_rx_ciph_rej(conn, msg, &tp);
- case BSS_MAP_MSG_ASSIGMENT_FAILURE:
- return bssmap_rx_ass_fail(conn, msg, &tp);
- case BSS_MAP_MSG_SAPI_N_REJECT:
- return bssmap_rx_sapi_n_rej(conn, msg, &tp);
- case BSS_MAP_MSG_ASSIGMENT_COMPLETE:
- return bssmap_rx_ass_compl(conn, msg, &tp);
- case BSS_MAP_MSG_LCLS_NOTIFICATION:
- return bssmap_rx_lcls_notif(conn, msg, &tp);
- default:
- LOGPCONN(conn, LOGL_ERROR, "Unimplemented msg type: %s\n", gsm0808_bssmap_name(msg_type));
- return -EINVAL;
- }
-
- return -EINVAL;
-}
-
-/* Endpoint to handle regular BSSAP DTAP messages. No ownership of 'msg' is passed on! */
-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 ran_conn *conn;
- struct dtap_header *dtap = (struct dtap_header *) msg->l2h;
-
- conn = ran_conn_lookup_a(network, a_conn_info->conn_id);
- if (!conn) {
- return -EINVAL;
- }
-
- LOGPCONN(conn, LOGL_DEBUG, "Rx DTAP %s\n", msgb_hexdump_l2(msg));
-
- /* ran_conn_dtap expects the dtap payload in l3h */
- msg->l3h = msg->l2h + 3;
- OMSC_LINKID_CB(msg) = dtap->link_id;
-
- /* Forward dtap payload into the msc */
- ran_conn_dtap(conn, msg);
-
- return 0;
-}
-
-/* Handle incoming connection oriented messages. No ownership of 'msg' is passed on! */
-int a_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);
-
- if (msgb_l2len(msg) < sizeof(struct bssmap_header)) {
- LOGP(DBSSAP, LOGL_NOTICE, "The header is too short -- discarding message!\n");
- return -EINVAL;
- }
-
- 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);
- case BSSAP_MSG_DTAP:
- return rx_dtap(scu, a_conn_info, msg);
- default:
- LOGP(DBSSAP, LOGL_ERROR, "Unimplemented BSSAP msg type: %s\n", gsm0808_bssap_name(msg->l2h[0]));
- return -EINVAL;
- }
-
- return -EINVAL;
-}
diff --git a/src/libmsc/a_reset.c b/src/libmsc/a_reset.c
deleted file mode 100644
index d6d4a1c5e..000000000
--- a/src/libmsc/a_reset.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/* (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/logging.h>
-#include <osmocom/core/utils.h>
-#include <osmocom/core/timer.h>
-#include <osmocom/core/fsm.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/a_reset.h>
-#include <osmocom/msc/msc_common.h>
-
-#define RESET_RESEND_INTERVAL 2 /* sec */
-#define RESET_RESEND_TIMER_NO 16 /* See also 3GPP TS 48.008 Chapter 3.1.4.1.3.2 */
-
-enum reset_fsm_states {
- ST_DISC, /* Disconnected from remote end */
- ST_CONN, /* We have a confirmed connection */
-};
-
-enum reset_fsm_evt {
- EV_CONN_ACK, /* Received either BSSMAP RESET or BSSMAP RESET
- * ACK from the remote end */
-};
-
-/* Reset context data (callbacks, state machine etc...) */
-struct reset_ctx {
- /* Callback function to be called when a connection
- * failure is detected and a rest must occur */
- void (*cb)(void *priv);
-
- /* Privated data for the callback function */
- void *priv;
-};
-
-static const struct value_string fsm_event_names[] = {
- OSMO_VALUE_STRING(EV_CONN_ACK),
- {0, NULL}
-};
-
-/* Disconnected state */
-static void fsm_disc_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- osmo_fsm_inst_state_chg(fi, ST_CONN, 0, 0);
-}
-
-/* Timer callback to retransmit the reset signal */
-static int fsm_reset_ack_timeout_cb(struct osmo_fsm_inst *fi)
-{
- struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv;
- LOGPFSML(fi, LOGL_NOTICE, "(re)sending BSSMAP RESET message...\n");
- reset_ctx->cb(reset_ctx->priv);
- osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO);
- return 0;
-}
-
-static struct osmo_fsm_state reset_fsm_states[] = {
- [ST_DISC] = {
- .in_event_mask = (1 << EV_CONN_ACK),
- .out_state_mask = (1 << ST_CONN) | (1 << ST_DISC),
- .name = "DISC",
- .action = fsm_disc_cb,
- },
- [ST_CONN] = {
- .in_event_mask = (1 << EV_CONN_ACK),
- .name = "CONN",
- },
-};
-
-/* State machine definition */
-static struct osmo_fsm fsm = {
- .name = "A-RESET",
- .states = reset_fsm_states,
- .num_states = ARRAY_SIZE(reset_fsm_states),
- .log_subsys = DMSC,
- .timer_cb = fsm_reset_ack_timeout_cb,
- .event_names = fsm_event_names,
-};
-
-/* Create and start state machine which handles the reset/reset-ack procedure */
-struct osmo_fsm_inst *a_reset_alloc(void *ctx, const char *name, void *cb,
- void *priv, bool already_connected)
-{
- OSMO_ASSERT(name);
-
- struct reset_ctx *reset_ctx;
- struct osmo_fsm_inst *reset_fsm;
-
- /* Register the fsm description (if not already done) */
- if (osmo_fsm_find_by_name(fsm.name) != &fsm)
- osmo_fsm_register(&fsm);
-
- /* Allocate and configure a new fsm instance */
- reset_ctx = talloc_zero(ctx, struct reset_ctx);
- OSMO_ASSERT(reset_ctx);
- reset_ctx->priv = priv;
- reset_ctx->cb = cb;
- reset_fsm = osmo_fsm_inst_alloc(&fsm, ctx, reset_ctx, LOGL_DEBUG, name);
- OSMO_ASSERT(reset_fsm);
-
- if (already_connected)
- osmo_fsm_inst_state_chg(reset_fsm, ST_CONN, 0, 0);
- else {
- /* kick off reset-ack sending mechanism */
- osmo_fsm_inst_state_chg(reset_fsm, ST_DISC, RESET_RESEND_INTERVAL,
- RESET_RESEND_TIMER_NO);
- }
-
- return reset_fsm;
-}
-
-/* Confirm that we sucessfully received a reset acknowlege message */
-void a_reset_ack_confirm(struct osmo_fsm_inst *reset_fsm)
-{
- OSMO_ASSERT(reset_fsm);
- osmo_fsm_inst_dispatch(reset_fsm, EV_CONN_ACK, NULL);
-}
-
-/* Check if we have a connection to a specified msc */
-bool a_reset_conn_ready(struct osmo_fsm_inst *reset_fsm)
-{
- /* If no reset context is supplied, we assume that
- * the connection can't be ready! */
- if (!reset_fsm)
- return false;
-
- if (reset_fsm->state == ST_CONN)
- return true;
-
- return false;
-}
diff --git a/src/libmsc/call_leg.c b/src/libmsc/call_leg.c
new file mode 100644
index 000000000..cb7e6ea80
--- /dev/null
+++ b/src/libmsc/call_leg.c
@@ -0,0 +1,348 @@
+/* Implementation to manage two RTP streams that make up an MO or MT call leg's RTP forwarding. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <osmocom/core/fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/msc_a.h>
+
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+
+#define LOG_CALL_LEG(cl, level, fmt, args...) \
+ LOGPFSML(cl ? cl->fi : NULL, level, fmt, ##args)
+
+static struct gsm_network *gsmnet = NULL;
+
+enum call_leg_state {
+ CALL_LEG_ST_ESTABLISHING,
+ CALL_LEG_ST_ESTABLISHED,
+ CALL_LEG_ST_RELEASING,
+};
+
+struct osmo_tdef g_mgw_tdefs[] = {
+ { .T=-1, .default_val=4, .desc="MGCP response timeout" },
+ { .T=-2, .default_val=30, .desc="RTP stream establishing timeout" },
+ {}
+};
+
+static const struct osmo_tdef_state_timeout call_leg_fsm_timeouts[32] = {
+ [CALL_LEG_ST_ESTABLISHING] = { .T = -2 },
+ [CALL_LEG_ST_RELEASING] = { .T = -2 },
+};
+
+#define call_leg_state_chg(cl, state) \
+ osmo_tdef_fsm_inst_state_chg((cl)->fi, state, call_leg_fsm_timeouts, g_mgw_tdefs, 10)
+
+static struct osmo_fsm call_leg_fsm;
+
+void call_leg_init(struct gsm_network *net)
+{
+ gsmnet = net;
+ OSMO_ASSERT( osmo_fsm_register(&call_leg_fsm) == 0 );
+}
+
+struct call_leg *call_leg_alloc(struct osmo_fsm_inst *parent_fi,
+ uint32_t parent_event_term,
+ uint32_t parent_event_rtp_addr_available,
+ uint32_t parent_event_rtp_complete,
+ uint32_t parent_event_rtp_released)
+{
+ struct call_leg *cl;
+ struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&call_leg_fsm, parent_fi, parent_event_term);
+
+ OSMO_ASSERT(fi);
+
+ cl = talloc(fi, struct call_leg);
+ OSMO_ASSERT(cl);
+ fi->priv = cl;
+ *cl = (struct call_leg){
+ .fi = fi,
+ .parent_event_rtp_addr_available = parent_event_rtp_addr_available,
+ .parent_event_rtp_complete = parent_event_rtp_complete,
+ .parent_event_rtp_released = parent_event_rtp_released,
+ };
+
+ return cl;
+}
+
+void call_leg_reparent(struct call_leg *cl,
+ struct osmo_fsm_inst *new_parent_fi,
+ uint32_t parent_event_term,
+ uint32_t parent_event_rtp_addr_available,
+ uint32_t parent_event_rtp_complete,
+ uint32_t parent_event_rtp_released)
+{
+ LOG_CALL_LEG(cl, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n",
+ cl->fi->proc.parent->name, new_parent_fi->name);
+ osmo_fsm_inst_change_parent(cl->fi, new_parent_fi, parent_event_term);
+ talloc_steal(new_parent_fi, cl->fi);
+ cl->parent_event_rtp_addr_available = parent_event_rtp_addr_available;
+ cl->parent_event_rtp_complete = parent_event_rtp_complete;
+ cl->parent_event_rtp_released = parent_event_rtp_released;
+}
+
+static int call_leg_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct call_leg *cl = fi->priv;
+ call_leg_release(cl);
+ return 0;
+}
+
+void call_leg_release(struct call_leg *cl)
+{
+ if (!cl)
+ return;
+ if (cl->fi->state == CALL_LEG_ST_RELEASING)
+ return;
+ call_leg_state_chg(cl, CALL_LEG_ST_RELEASING);
+}
+
+static void call_leg_mgw_endpoint_gone(struct call_leg *cl)
+{
+ int i;
+ cl->mgw_endpoint = NULL;
+ for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
+ if (!cl->rtp[i])
+ continue;
+ cl->rtp[i]->ci = NULL;
+ }
+}
+
+static void call_leg_fsm_establishing_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct call_leg *cl = fi->priv;
+ struct rtp_stream *rtps;
+ int i;
+ bool established;
+
+ switch (event) {
+
+ case CALL_LEG_EV_RTP_STREAM_ESTABLISHED:
+ /* An rtp_stream says it is established. If all are now established, change to state
+ * CALL_LEG_ST_ESTABLISHED. */
+ established = true;
+ for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
+ if (!rtp_stream_is_established(cl->rtp[i])) {
+ established = false;
+ break;
+ }
+ }
+ if (!established)
+ break;
+ call_leg_state_chg(cl, CALL_LEG_ST_ESTABLISHED);
+ break;
+
+ case CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE:
+ rtps = data;
+ osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_addr_available, rtps);
+ break;
+
+ case CALL_LEG_EV_RTP_STREAM_GONE:
+ call_leg_release(cl);
+ break;
+
+ case CALL_LEG_EV_MGW_ENDPOINT_GONE:
+ call_leg_mgw_endpoint_gone(cl);
+ call_leg_release(cl);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+void call_leg_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct call_leg *cl = fi->priv;
+ osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_complete, cl);
+}
+
+void call_leg_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void call_leg_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct call_leg *cl = fi->priv;
+
+ switch (event) {
+
+ case CALL_LEG_EV_RTP_STREAM_GONE:
+ /* We're already terminating, child RTP streams will also terminate, there is nothing left to do. */
+ break;
+
+ case CALL_LEG_EV_MGW_ENDPOINT_GONE:
+ call_leg_mgw_endpoint_gone(cl);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct value_string call_leg_fsm_event_names[] = {
+ OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE),
+ OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ESTABLISHED),
+ OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_GONE),
+ OSMO_VALUE_STRING(CALL_LEG_EV_MGW_ENDPOINT_GONE),
+ {}
+};
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state call_leg_fsm_states[] = {
+ [CALL_LEG_ST_ESTABLISHING] = {
+ .name = "ESTABLISHING",
+ .in_event_mask = 0
+ | S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
+ | S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
+ | S(CALL_LEG_EV_RTP_STREAM_GONE)
+ | S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
+ ,
+ .out_state_mask = 0
+ | S(CALL_LEG_ST_ESTABLISHED)
+ | S(CALL_LEG_ST_RELEASING)
+ ,
+ .action = call_leg_fsm_establishing_established,
+ },
+ [CALL_LEG_ST_ESTABLISHED] = {
+ .name = "ESTABLISHED",
+ .in_event_mask = 0
+ | S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
+ | S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
+ | S(CALL_LEG_EV_RTP_STREAM_GONE)
+ | S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
+ ,
+ .out_state_mask = 0
+ | S(CALL_LEG_ST_ESTABLISHING)
+ | S(CALL_LEG_ST_RELEASING)
+ ,
+ .onenter = call_leg_fsm_established_onenter,
+ .action = call_leg_fsm_establishing_established, /* same action function as above */
+ },
+ [CALL_LEG_ST_RELEASING] = {
+ .name = "RELEASING",
+ .in_event_mask = 0
+ | S(CALL_LEG_EV_RTP_STREAM_GONE)
+ | S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
+ ,
+ .onenter = call_leg_fsm_releasing_onenter,
+ .action = call_leg_fsm_releasing,
+ },
+};
+
+static struct osmo_fsm call_leg_fsm = {
+ .name = "call_leg",
+ .states = call_leg_fsm_states,
+ .num_states = ARRAY_SIZE(call_leg_fsm_states),
+ .log_subsys = DCC,
+ .event_names = call_leg_fsm_event_names,
+ .timer_cb = call_leg_fsm_timer_cb,
+};
+
+const struct value_string rtp_direction_names[] = {
+ OSMO_VALUE_STRING(RTP_TO_RAN),
+ OSMO_VALUE_STRING(RTP_TO_CN),
+ {}
+};
+
+int call_leg_ensure_rtp_alloc(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans)
+{
+ if (cl->rtp[dir])
+ return 0;
+
+ if (!cl->mgw_endpoint)
+ cl->mgw_endpoint = osmo_mgcpc_ep_alloc(cl->fi, CALL_LEG_EV_MGW_ENDPOINT_GONE,
+ gsmnet->mgw.client, gsmnet->mgw.tdefs, cl->fi->id,
+ "%s", mgcp_client_rtpbridge_wildcard(gsmnet->mgw.client));
+ if (!cl->mgw_endpoint) {
+ LOG_CALL_LEG(cl, LOGL_ERROR, "failed to setup MGW endpoint\n");
+ return -EIO;
+ }
+
+ cl->rtp[dir] = rtp_stream_alloc(cl, dir, call_id, for_trans);
+ OSMO_ASSERT(cl->rtp[dir]);
+ return 0;
+}
+
+struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direction dir)
+{
+ struct rtp_stream *rtps;
+ if (!cl)
+ return NULL;
+ rtps = cl->rtp[dir];
+ if (!rtps)
+ return NULL;
+ if (!osmo_sockaddr_str_is_set(&rtps->local))
+ return NULL;
+ return &rtps->local;
+}
+
+/* Make sure an MGW endpoint CI is set up for an RTP connection.
+ * This is the one-stop for all to either completely set up a new endpoint connection, or to modify an existing one.
+ * If not yet present, allocate the rtp_stream for the given direction.
+ * Then, call rtp_stream_set_codec() if codec_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if
+ * remote_addr_if_known is non-NULL.
+ * Finally make sure that a CRCX is sent out for this direction, if this has not already happened.
+ * If the CRCX has already happened but new codec / remote_addr data was passed, call rtp_stream_commit() to trigger an
+ * MDCX.
+ */
+int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans,
+ const enum mgcp_codecs *codec_if_known, const struct osmo_sockaddr_str *remote_addr_if_known)
+{
+ if (call_leg_ensure_rtp_alloc(cl, dir, call_id, for_trans))
+ return -EIO;
+ cl->rtp[dir]->crcx_conn_mode = cl->crcx_conn_mode[dir];
+ if (codec_if_known)
+ rtp_stream_set_codec(cl->rtp[dir], *codec_if_known);
+ if (remote_addr_if_known && osmo_sockaddr_str_is_set(remote_addr_if_known))
+ rtp_stream_set_remote_addr(cl->rtp[dir], remote_addr_if_known);
+ return rtp_stream_ensure_ci(cl->rtp[dir], cl->mgw_endpoint);
+}
+
+int call_leg_local_bridge(struct call_leg *cl1, uint32_t call_id1, struct gsm_trans *trans1,
+ struct call_leg *cl2, uint32_t call_id2, struct gsm_trans *trans2)
+{
+ enum mgcp_codecs codec;
+
+ cl1->local_bridge = cl2;
+ cl2->local_bridge = cl1;
+
+ /* We may just copy the codec info we have for the RAN side of the first leg to the CN side of both legs. This
+ * also means that if both legs use different codecs the MGW must perform transcoding on the second leg. */
+ if (!cl1->rtp[RTP_TO_RAN] || !cl1->rtp[RTP_TO_RAN]->codec_known) {
+ LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side RTP stream codec is not known, not ready for bridging\n");
+ return -EINVAL;
+ }
+ codec = cl1->rtp[RTP_TO_RAN]->codec;
+
+ call_leg_ensure_ci(cl1, RTP_TO_CN, call_id1, trans1,
+ &codec, &cl2->rtp[RTP_TO_CN]->local);
+ call_leg_ensure_ci(cl2, RTP_TO_CN, call_id2, trans2,
+ &codec, &cl1->rtp[RTP_TO_CN]->local);
+ return 0;
+}
diff --git a/src/libmsc/cell_id_list.c b/src/libmsc/cell_id_list.c
new file mode 100644
index 000000000..ca7a6d43b
--- /dev/null
+++ b/src/libmsc/cell_id_list.c
@@ -0,0 +1,79 @@
+/* Manage a list of struct gsm0808_cell_id */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <osmocom/msc/cell_id_list.h>
+
+int cell_id_list_add_cell(void *talloc_ctx, struct llist_head *list, const struct gsm0808_cell_id *cid)
+{
+ struct cell_id_list_entry *e = cell_id_list_find(list, cid, 0, true);
+
+ if (e)
+ return 0;
+
+ e = talloc(talloc_ctx, struct cell_id_list_entry);
+ OSMO_ASSERT(e);
+ *e = (struct cell_id_list_entry){
+ .cell_id = *cid,
+ };
+ llist_add_tail(&e->entry, list);
+ return 1;
+}
+
+int cell_id_list_add_list(void *talloc_ctx, struct llist_head *list, const struct gsm0808_cell_id_list2 *cil)
+{
+ struct gsm0808_cell_id one_id;
+ int i;
+ int added = 0;
+ for (i = 0; i < cil->id_list_len; i++) {
+ one_id = (struct gsm0808_cell_id){
+ .id_discr = cil->id_discr,
+ .id = cil->id_list[i],
+ };
+ added += cell_id_list_add_cell(talloc_ctx, list, &one_id);
+ }
+ return added;
+}
+
+void cell_id_list_del_entry(struct cell_id_list_entry *e)
+{
+ llist_del(&e->entry);
+ talloc_free(e);
+}
+
+struct cell_id_list_entry *cell_id_list_find(struct llist_head *list,
+ const struct gsm0808_cell_id *id,
+ unsigned int match_nr,
+ bool exact_match)
+{
+ struct cell_id_list_entry *e;
+ llist_for_each_entry(e, list, entry) {
+ if (gsm0808_cell_ids_match(id, &e->cell_id, exact_match)) {
+ if (match_nr)
+ match_nr--;
+ else
+ return e;
+ }
+ }
+ return NULL;
+}
diff --git a/src/libmsc/e_link.c b/src/libmsc/e_link.c
new file mode 100644
index 000000000..685ca7cfe
--- /dev/null
+++ b/src/libmsc/e_link.c
@@ -0,0 +1,372 @@
+/* E-interface messaging over a GSUP connection */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsupclient/gsup_client.h>
+
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/gsup_client_mux.h>
+#include <osmocom/msc/e_link.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/ran_infra.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_a_remote.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/msc_i_remote.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/msc_t_remote.h>
+
+#define LOG_E_LINK(e_link, level, fmt, args...) \
+ LOGPFSML(e_link->msc_role, level, fmt, ##args)
+
+#define LOG_E_LINK_CAT(e_link, ss, level, fmt, args...) \
+ LOGPFSMSL(e_link->msc_role, ss, level, fmt, ##args)
+
+void e_link_assign(struct e_link *e, struct osmo_fsm_inst *msc_role)
+{
+ struct msc_role_common *c;
+ if (e->msc_role) {
+ c = e->msc_role->priv;
+ if (c->remote_to == e) {
+ c->remote_to = NULL;
+ msub_update_id(c->msub);
+ }
+ }
+
+ c = msc_role->priv;
+ e->msc_role = msc_role;
+ c->remote_to = e;
+
+ msub_update_id(c->msub);
+ LOG_E_LINK(e, LOGL_DEBUG, "Assigned E-link to %s\n", e_link_name(e));
+}
+
+struct e_link *e_link_alloc(struct gsup_client_mux *gcm, struct osmo_fsm_inst *msc_role,
+ const uint8_t *remote_name, size_t remote_name_len)
+{
+ struct e_link *e;
+ struct msc_role_common *c = msc_role->priv;
+
+ /* use msub as talloc parent, so we can move an e_link from msc_t to msc_i when it is established. */
+ e = talloc_zero(c->msub, struct e_link);
+ if (!e)
+ return NULL;
+
+ e->gcm = gcm;
+
+ /* Expecting all code paths to print the remote name according to remote_name_len. To be paranoid, place a nul
+ * character after the end. */
+ e->remote_name = talloc_size(e, remote_name_len + 1);
+ OSMO_ASSERT(e->remote_name);
+ memcpy(e->remote_name, remote_name, remote_name_len);
+ e->remote_name[remote_name_len] = '\0';
+ e->remote_name_len = remote_name_len;
+
+ e_link_assign(e, msc_role);
+ return e;
+}
+
+void e_link_free(struct e_link *e)
+{
+ if (!e)
+ return;
+ if (e->msc_role) {
+ struct msc_role_common *c = e->msc_role->priv;
+ if (c->remote_to == e)
+ c->remote_to = NULL;
+ }
+ talloc_free(e);
+}
+
+/* Set up IMSI, source and destination names in given gsup_msg struct. */
+int e_prep_gsup_msg(struct e_link *e, struct osmo_gsup_message *gsup_msg)
+{
+ struct msc_role_common *c;
+ struct vlr_subscr *vsub;
+ const char *local_msc_name = NULL;
+
+ if (e->gcm && e->gcm->gsup_client && e->gcm->gsup_client->ipa_dev) {
+ local_msc_name = e->gcm->gsup_client->ipa_dev->serno;
+ if (!local_msc_name)
+ local_msc_name = e->gcm->gsup_client->ipa_dev->unit_name;
+ }
+
+ if (!local_msc_name) {
+ LOG_E_LINK(e, LOGL_ERROR, "Cannot prep E-interface GSUP message: no local MSC name defined\n");
+ return -ENODEV;
+ }
+
+ c = e->msc_role->priv;
+ vsub = c->msub->vsub;
+ *gsup_msg = (struct osmo_gsup_message){
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC,
+ .source_name = (const uint8_t*)local_msc_name,
+ .source_name_len = strlen(local_msc_name),
+ .destination_name = (const uint8_t*)e->remote_name,
+ .destination_name_len = e->remote_name_len,
+ };
+
+ if (vsub)
+ OSMO_STRLCPY_ARRAY(gsup_msg->imsi, vsub->imsi);
+ return 0;
+}
+
+int e_tx(struct e_link *e, const struct osmo_gsup_message *gsup_msg)
+{
+ LOG_E_LINK_CAT(e, DLGSUP, LOGL_DEBUG, "Tx GSUP %s to %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type),
+ e_link_name(e));
+ return gsup_client_mux_tx(e->gcm, gsup_msg);
+}
+
+const char *e_link_name(struct e_link *e)
+{
+ return osmo_escape_str((const char*)e->remote_name, e->remote_name_len);
+}
+
+static struct msub *msc_new_msc_t_for_handover_request(struct gsm_network *net,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ struct ran_infra *ran;
+ struct msub *msub;
+ struct msc_a *msc_a;
+ struct vlr_subscr *vsub;
+
+ switch (gsup_msg->an_apdu.access_network_proto) {
+ case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006:
+ ran = &msc_ran_infra[OSMO_RAT_GERAN_A];
+ break;
+ case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413:
+ ran = &msc_ran_infra[OSMO_RAT_UTRAN_IU];
+ break;
+ default:
+ ran = NULL;
+ break;
+ }
+
+ if (!ran || !ran->ran_dec_l2) {
+ LOGP(DLGSUP, LOGL_ERROR, "Cannot handle AN-proto %s\n",
+ an_proto_name(gsup_msg->an_apdu.access_network_proto));
+ return NULL;
+ }
+
+ msub = msub_alloc(net);
+
+ /* To properly compose GSUP messages going back to the remote peer, make sure the incoming IMSI is set in a
+ * vlr_subscr associated with the msub. */
+ vsub = vlr_subscr_find_or_create_by_imsi(net->vlr, gsup_msg->imsi, __func__, NULL);
+ msub_set_vsub(msub, vsub);
+ vlr_subscr_put(vsub, __func__);
+
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n");
+
+ msc_a = msc_a_remote_alloc(msub, ran, gsup_msg->source_name, gsup_msg->source_name_len);
+ if (!msc_a) {
+ osmo_fsm_inst_term(msub->fi, OSMO_FSM_TERM_REQUEST, NULL);
+ return NULL;
+ }
+
+ LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n");
+ return msub;
+}
+
+static bool name_matches(const uint8_t *name, size_t len, const uint8_t *match_name, size_t match_len)
+{
+ if (!match_name)
+ return !name || !len;
+ if (len != match_len)
+ return false;
+ return memcmp(name, match_name, len) == 0;
+}
+
+static bool e_link_matches_gsup_msg_source_name(const struct e_link *e, const struct osmo_gsup_message *gsup_msg)
+{
+ return name_matches(gsup_msg->source_name, gsup_msg->source_name_len, e->remote_name, e->remote_name_len);
+}
+
+static int msc_a_i_t_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg)
+{
+ struct gsm_network *net = data;
+ struct vlr_instance *vlr = net->vlr;
+ struct vlr_subscr *vsub;
+ struct msub *msub;
+ struct osmo_fsm_inst *msc_role = NULL;
+ struct e_link *e;
+ struct msc_role_common *c;
+ int i;
+
+ OSMO_ASSERT(net);
+
+ vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__);
+ if (vsub)
+ LOGP(DLGSUP, LOGL_DEBUG, "Found VLR entry for IMSI %s\n", gsup_msg->imsi);
+
+ msub = msub_for_vsub(vsub);
+ if (msub)
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Found already attached subscriber for IMSI %s\n",
+ gsup_msg->imsi);
+
+ if (vsub) {
+ vlr_subscr_put(vsub, __func__);
+ vsub = NULL;
+ }
+
+ /* Only for an incoming Handover Request: create a new remote-MSC-A as proxy for the MSC-A that is sending the
+ * Handover Request */
+ if (!msub && gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) {
+ msub = msc_new_msc_t_for_handover_request(net, gsup_msg);
+ }
+
+ if (!msub) {
+ LOGP(DLGSUP, LOGL_ERROR, "%s: Cannot find subscriber for IMSI %s\n",
+ __func__, osmo_quote_str(gsup_msg->imsi, -1));
+ return -EINVAL;
+ }
+
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Rx GSUP %s\n", osmo_gsup_message_type_name(gsup_msg->message_type));
+
+ e = NULL;
+ for (i = 0; i < ARRAY_SIZE(msub->role); i++) {
+ msc_role = msub->role[i];
+ if (!msc_role) {
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "No %s\n", msc_role_name(i));
+ continue;
+ }
+ c = msc_role->priv;
+ if (!c->remote_to) {
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has no remote\n", msc_role_name(i));
+ continue;
+ }
+ if (!e_link_matches_gsup_msg_source_name(c->remote_to, gsup_msg)) {
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has remote to mismatching %s\n", msc_role_name(i),
+ c->remote_to->remote_name);
+ continue;
+ }
+ /* Found a match. */
+ e = c->remote_to;
+ break;
+ }
+
+ if (!e) {
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_ERROR,
+ "There is no E link that matches: Rx GSUP %s from %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type),
+ osmo_quote_str((const char*)gsup_msg->source_name, gsup_msg->source_name_len));
+ return -EINVAL;
+ }
+
+ LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG,
+ "Rx GSUP %s from %s %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type),
+ msc_role_name(c->role),
+ e_link_name(e));
+
+ return osmo_fsm_inst_dispatch(msc_role, MSC_REMOTE_EV_RX_GSUP, (void*)gsup_msg);
+}
+
+void msc_a_i_t_gsup_init(struct gsm_network *net)
+{
+ OSMO_ASSERT(net->gcm);
+ OSMO_ASSERT(net->vlr);
+
+ net->gcm->rx_cb[OSMO_GSUP_MESSAGE_CLASS_INTER_MSC] = (struct gsup_client_mux_rx_cb){
+ .func = msc_a_i_t_gsup_rx,
+ .data = net,
+ };
+}
+
+int gsup_msg_assign_an_apdu(struct osmo_gsup_message *gsup_msg, struct an_apdu *an_apdu)
+{
+ if (!an_apdu) {
+ LOGP(DLGSUP, LOGL_ERROR, "Cannot assign NULL AN-APDU\n");
+ return -EINVAL;
+ }
+
+ gsup_msg->an_apdu = (struct osmo_gsup_an_apdu){
+ .access_network_proto = an_apdu->an_proto,
+ };
+
+ if (an_apdu->msg) {
+ gsup_msg->an_apdu.data = msgb_l2(an_apdu->msg);
+ gsup_msg->an_apdu.data_len = msgb_l2len(an_apdu->msg);
+ if (!gsup_msg->an_apdu.data || !gsup_msg->an_apdu.data_len) {
+ LOGP(DLGSUP, LOGL_ERROR, "Cannot assign AN-APDU without msg->l2 to GSUP message: %s\n",
+ msgb_hexdump(an_apdu->msg));
+ return -EINVAL;
+ }
+ }
+
+ /* We are composing a struct osmo_gsup_msg from the osmo-msc internal struct an_apdu. The an_apdu may contain
+ * additional info in form of a partly filled an_apdu->e_info. Make sure that data ends up in the resulting full
+ * osmo_gsup_message. */
+ if (an_apdu->e_info) {
+ const struct osmo_gsup_message *s = an_apdu->e_info;
+
+ gsup_msg->msisdn_enc = s->msisdn_enc;
+ gsup_msg->msisdn_enc_len = s->msisdn_enc_len;
+
+ if (s->cause_rr_set) {
+ gsup_msg->cause_rr = s->cause_rr;
+ gsup_msg->cause_rr_set = true;
+ }
+ if (s->cause_bssap_set) {
+ gsup_msg->cause_bssap = s->cause_bssap;
+ gsup_msg->cause_bssap_set = true;
+ }
+ if (s->cause_sm)
+ gsup_msg->cause_sm = s->cause_sm;
+ }
+ return 0;
+}
+
+/* Allocate a new msgb to contain the gsup_msg->an_apdu's data as l2h.
+ * The msgb will have sufficient headroom to be passed down a RAN peer's SCCP user SAP. */
+struct msgb *gsup_msg_to_msgb(const struct osmo_gsup_message *gsup_msg)
+{
+ struct msgb *pdu;
+ const uint8_t *pdu_data = gsup_msg->an_apdu.data;
+ uint8_t pdu_len = gsup_msg->an_apdu.data_len;
+
+ if (!pdu_data || !pdu_len)
+ return NULL;
+
+ /* Strictly speaking this is not limited to BSSMAP, but why not just use those sizes. */
+ pdu = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "AN-APDU from gsup_msg");
+
+ pdu->l2h = msgb_put(pdu, pdu_len);
+ memcpy(pdu->l2h, pdu_data, pdu_len);
+ return pdu;
+}
+
+/* Compose a struct an_apdu from the data found in gsup_msg. gsup_msg_to_msgb() is used to wrap the data in a static
+ * msgb, so the returned an_apdu->msg must be freed if not NULL. */
+void gsup_msg_to_an_apdu(struct an_apdu *an_apdu, const struct osmo_gsup_message *gsup_msg)
+{
+ *an_apdu = (struct an_apdu){
+ .an_proto = gsup_msg->an_apdu.access_network_proto,
+ .msg = gsup_msg_to_msgb(gsup_msg),
+ .e_info = gsup_msg,
+ };
+}
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index 4be42e979..667a1c605 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -50,7 +50,8 @@
#include <osmocom/abis/e1_input.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/paging.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm0480.h>
@@ -62,13 +63,9 @@
#include <osmocom/core/byteswap.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/crypt/auth.h>
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#endif
-#include <osmocom/msc/msc_ifaces.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/msc_mgcp.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_roles.h>
#include <assert.h>
@@ -76,8 +73,7 @@
void *tall_locop_ctx;
void *tall_authciphop_ctx;
-static int gsm0408_loc_upd_acc(struct ran_conn *conn,
- uint32_t send_tmsi);
+static int gsm0408_loc_upd_acc(struct msc_a *msc_a, uint32_t send_tmsi);
/*! Send a simple GSM 04.08 message without any payload
* \param conn Active RAN connection
@@ -85,8 +81,7 @@ static int gsm0408_loc_upd_acc(struct ran_conn *conn,
* \param[in] msg_type Message type
* \return result of \ref gsm48_conn_sendmsg
*/
-int gsm48_tx_simple(struct ran_conn *conn,
- uint8_t pdisc, uint8_t msg_type)
+int gsm48_tx_simple(struct msc_a *msc_a, uint8_t pdisc, uint8_t msg_type)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 TX SIMPLE");
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
@@ -94,145 +89,18 @@ int gsm48_tx_simple(struct ran_conn *conn,
gh->proto_discr = pdisc;
gh->msg_type = msg_type;
- return gsm48_conn_sendmsg(msg, conn, NULL);
-}
-
-static bool classmark1_is_r99(const struct gsm48_classmark1 *cm1)
-{
- return cm1->rev_lev >= 2;
-}
-
-static bool classmark2_is_r99(const uint8_t *cm2, uint8_t cm2_len)
-{
- uint8_t rev_lev;
- if (!cm2_len)
- return false;
- rev_lev = (cm2[0] >> 5) & 0x3;
- return rev_lev >= 2;
-}
-
-static bool classmark_is_r99(struct gsm_classmark *cm)
-{
- if (cm->classmark1_set)
- return classmark1_is_r99(&cm->classmark1);
- return classmark2_is_r99(cm->classmark2, cm->classmark2_len);
-}
-
-static const char *classmark_a5_name(const struct gsm_classmark *cm)
-{
- static char buf[128];
- char cm1[42];
- char cm2[42];
- char cm3[42];
-
- if (cm->classmark1_set)
- snprintf(cm1, sizeof(cm1), "cm1{a5/1=%s}",
- cm->classmark1.a5_1 ? "not-supported":"supported" /* inverted logic */);
- else
- snprintf(cm1, sizeof(cm1), "no-cm1");
-
- if (cm->classmark2_len >= 3)
- snprintf(cm2, sizeof(cm2), " cm2{0x%x=%s%s}",
- cm->classmark2[2],
- cm->classmark2[2] & 0x1 ? " A5/2" : "",
- cm->classmark2[2] & 0x2 ? " A5/3" : "");
- else
- snprintf(cm2, sizeof(cm2), " no-cm2");
-
- if (cm->classmark3_len >= 1)
- snprintf(cm3, sizeof(cm3), " cm3{0x%x=%s%s%s%s}",
- cm->classmark3[0],
- cm->classmark3[0] & (1 << 0) ? " A5/4" : "",
- cm->classmark3[0] & (1 << 1) ? " A5/5" : "",
- cm->classmark3[0] & (1 << 2) ? " A5/6" : "",
- cm->classmark3[0] & (1 << 3) ? " A5/7" : "");
- else
- snprintf(cm3, sizeof(cm3), " no-cm3");
-
- snprintf(buf, sizeof(buf), "%s%s%s", cm1, cm2, cm3);
- return buf;
-}
-
-/* Determine if the given CLASSMARK (1/2/3) value permits a given A5/n cipher.
- * Return 1 when the given A5/n is permitted, 0 when not, and negative if the respective MS CLASSMARK is
- * not known, where the negative number indicates the classmark type: -2 means Classmark 2 is not
- * available. */
-static int classmark_supports_a5(const struct gsm_classmark *cm, uint8_t a5)
-{
- switch (a5) {
- case 0:
- /* all phones must implement A5/0, see 3GPP TS 43.020 4.9 */
- return 1;
- case 1:
- /* 3GPP TS 43.020 4.9 requires A5/1 to be suppored by all phones and actually states:
- * "The network shall not provide service to an MS which indicates that it does not
- * support the ciphering algorithm A5/1.". However, let's be more tolerant based
- * on policy here */
- /* See 3GPP TS 24.008 10.5.1.7 */
- if (!cm->classmark1_set) {
- DEBUGP(DMSC, "CLASSMARK 1 unknown, assuming MS supports A5/1\n");
- return -1;
- } else {
- if (cm->classmark1.a5_1)
- return 0; /* Inverted logic for this bit! */
- else
- return 1;
- }
- break;
- case 2:
- case 3:
- /* See 3GPP TS 24.008 10.5.1.6 */
- if (cm->classmark2_len < 3) {
- return -2;
- } else {
- if (cm->classmark2[2] & (1 << (a5-2)))
- return 1;
- else
- return 0;
- }
- break;
- case 4:
- case 5:
- case 6:
- case 7:
- /* See 3GPP TS 24.008 10.5.1.7 */
- if (cm->classmark3_len < 1) {
- return -3;
- } else {
- if (cm->classmark3[0] & (1 << (a5-4)))
- return 1;
- else
- return 0;
- }
- break;
- default:
- return false;
- }
-}
-
-int gsm48_conn_sendmsg(struct msgb *msg, struct ran_conn *conn, struct gsm_trans *trans)
-{
- struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
-
- /* if we get passed a transaction reference, do some common
- * work that the caller no longer has to do */
- if (trans) {
- gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
- OMSC_LINKID_CB(msg) = trans->dlci;
- }
-
- return msc_tx_dtap(conn, msg);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
/* clear all transactions globally; used in case of MNCC socket disconnect */
-void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
+void gsm0408_clear_all_trans(struct gsm_network *net, enum trans_type type)
{
struct gsm_trans *trans, *temp;
LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n");
llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) {
- if (trans->protocol == protocol) {
+ if (trans->type == type) {
trans->callref = 0;
trans_free(trans);
}
@@ -240,33 +108,33 @@ void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
}
/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
-static int gsm0408_loc_upd_rej(struct ran_conn *conn, uint8_t cause)
+static int gsm0408_loc_upd_rej(struct msc_a *msc_a, uint8_t cause)
{
struct msgb *msg;
msg = gsm48_create_loc_upd_rej(cause);
if (!msg) {
- LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
return -1;
}
- LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT\n",
- vlr_subscr_name(conn->vsub));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_INFO, "LOCATION UPDATING REJECT\n");
- return gsm48_conn_sendmsg(msg, conn, NULL);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
-static int gsm0408_loc_upd_acc(struct ran_conn *conn,
- uint32_t send_tmsi)
+static int gsm0408_loc_upd_acc(struct msc_a *msc_a, uint32_t send_tmsi)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD ACC");
struct gsm48_hdr *gh;
struct gsm48_loc_area_id *lai;
uint8_t *mid;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
struct osmo_location_area_id laid = {
- .plmn = conn->network->plmn,
- .lac = conn->lac,
+ .plmn = net->plmn,
+ .lac = vsub->cgi.lai.lac,
};
gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
@@ -282,11 +150,11 @@ static int gsm0408_loc_upd_acc(struct ran_conn *conn,
* old TMSI that might still be allocated */
uint8_t mi[10];
int len;
- len = gsm48_generate_mid_from_imsi(mi, conn->vsub->imsi);
+ len = gsm48_generate_mid_from_imsi(mi, vsub->imsi);
mid = msgb_put(msg, len);
memcpy(mid, mi, len);
DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT\n",
- vlr_subscr_name(conn->vsub));
+ vlr_subscr_name(vsub));
} else {
/* Include the TMSI, which means that the MS will send a
* TMSI REALLOCATION COMPLETE, and we should wait for
@@ -294,7 +162,7 @@ static int gsm0408_loc_upd_acc(struct ran_conn *conn,
mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
gsm48_generate_mid_from_tmsi(mid, send_tmsi);
DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT (TMSI = 0x%08x)\n",
- vlr_subscr_name(conn->vsub),
+ vlr_subscr_name(vsub),
send_tmsi);
}
/* TODO: Follow-on proceed */
@@ -304,11 +172,11 @@ static int gsm0408_loc_upd_acc(struct ran_conn *conn,
/* TODO: Per-MS T3312 */
- return gsm48_conn_sendmsg(msg, conn, NULL);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
/* Transmit Chapter 9.2.10 Identity Request */
-static int mm_tx_identity_req(struct ran_conn *conn, uint8_t id_type)
+static int mm_tx_identity_req(struct msc_a *msc_a, uint8_t id_type)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ID REQ");
struct gsm48_hdr *gh;
@@ -318,17 +186,18 @@ static int mm_tx_identity_req(struct ran_conn *conn, uint8_t id_type)
gh->msg_type = GSM48_MT_MM_ID_REQ;
gh->data[0] = id_type;
- return gsm48_conn_sendmsg(msg, conn, NULL);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
/* Parse Chapter 9.2.11 Identity Response */
-static int mm_rx_id_resp(struct ran_conn *conn, struct msgb *msg)
+static int mm_rx_id_resp(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t *mi = gh->data+1;
uint8_t mi_len = gh->data[0];
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
- if (!conn->vsub) {
+ if (!vsub) {
LOGP(DMM, LOGL_ERROR,
"Rx MM Identity Response: invalid: no subscriber\n");
return -EINVAL;
@@ -338,14 +207,98 @@ static int mm_rx_id_resp(struct ran_conn *conn, struct msgb *msg)
osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
- return vlr_subscr_rx_id_resp(conn->vsub, mi, mi_len);
+ return vlr_subscr_rx_id_resp(vsub, mi, mi_len);
+}
+
+/* 9.2.5 CM service accept */
+static int msc_gsm48_tx_mm_serv_ack(struct msc_a *msc_a)
+{
+ struct msgb *msg;
+ struct gsm48_hdr *gh;
+
+ 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;
+
+ return msc_a_tx_dtap_to_i(msc_a, msg);
+}
+
+/* 9.2.6 CM service reject */
+static int msc_gsm48_tx_mm_serv_rej(struct msc_a *msc_a,
+ enum gsm48_reject_value value)
+{
+ struct msgb *msg;
+
+ msg = gsm48_create_mm_serv_rej(value);
+ if (!msg) {
+ LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n");
+ return -1;
+ }
+
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "-> CM SERVICE Reject cause: %d\n", value);
+
+ return msc_a_tx_dtap_to_i(msc_a, msg);
+}
+
+
+/* Extract the R99 flag from various Complete Level 3 messages. Depending on the Message Type, extract R99 from the
+ * Classmark Type 1 or the Classmark Type 2. Return 1 for R99, 0 for pre-R99, negative if not a valid Complete Level 3
+ * message. */
+int compl_l3_msg_is_r99(const struct msgb *msg)
+{
+ const struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ const struct gsm48_loc_upd_req *lu;
+ const struct gsm48_imsi_detach_ind *idi;
+ uint8_t cm2_len;
+ const struct gsm48_classmark2 *cm2;
+
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ switch (gsm48_hdr_msg_type(gh)) {
+ case GSM48_MT_MM_LOC_UPD_REQUEST:
+ lu = (const struct gsm48_loc_upd_req *) gh->data;
+ return osmo_gsm48_classmark1_is_r99(&lu->classmark1) ? 1 : 0;
+
+ case GSM48_MT_MM_CM_SERV_REQ:
+ case GSM48_MT_MM_CM_REEST_REQ:
+ break;
+
+ case GSM48_MT_MM_IMSI_DETACH_IND:
+ idi = (const struct gsm48_imsi_detach_ind *) gh->data;
+ return osmo_gsm48_classmark1_is_r99(&idi->classmark1) ? 1 : 0;
+
+ default:
+ return -1;
+ }
+ break;
+
+ case GSM48_PDISC_RR:
+ switch (gsm48_hdr_msg_type(gh)) {
+ case GSM48_MT_RR_PAG_RESP:
+ break;
+
+ default:
+ return -1;
+ }
+ break;
+
+ default:
+ return -1;
+ }
+
+ /* Both CM Service Request and Paging Response have Classmark Type 2 at the same location: */
+ cm2_len = gh->data[1];
+ cm2 = (void*)gh->data+2;
+ return osmo_gsm48_classmark2_is_r99(cm2, cm2_len) ? 1 : 0;
}
/* Chapter 9.2.15: Receive Location Updating Request.
* Keep this function non-static for direct invocation by unit tests. */
-int mm_rx_loc_upd_req(struct ran_conn *conn, struct msgb *msg)
+static int mm_rx_loc_upd_req(struct msc_a *msc_a, struct msgb *msg)
{
- struct gsm_network *net = conn->network;
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_loc_upd_req *lu;
uint8_t mi_type;
@@ -353,47 +306,49 @@ int mm_rx_loc_upd_req(struct ran_conn *conn, struct msgb *msg)
enum vlr_lu_type vlr_lu_type = VLR_LU_TYPE_REGULAR;
uint32_t tmsi;
char *imsi;
- struct osmo_location_area_id old_lai, new_lai;
+ struct osmo_location_area_id old_lai;
struct osmo_fsm_inst *lu_fsm;
bool is_utran;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub;
lu = (struct gsm48_loc_upd_req *) gh->data;
- if (ran_conn_is_establishing_auth_ciph(conn)) {
- LOG_RAN_CONN_CAT(conn, DMM, LOGL_ERROR,
- "Cannot accept another LU, conn already busy establishing authenticity;"
- " extraneous LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n",
- osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type));
+ if (msc_a_is_establishing_auth_ciph(msc_a)) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR,
+ "Cannot accept another LU, conn already busy establishing authenticity;"
+ " extraneous LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n",
+ osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type));
return -EINVAL;
}
- if (ran_conn_is_accepted(conn)) {
- LOG_RAN_CONN_CAT(conn, DMM, LOGL_ERROR,
- "Cannot accept another LU, conn already established;"
- " extraneous LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n",
- osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type));
+ if (msc_a_is_accepted(msc_a)) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR,
+ "Cannot accept another LU, conn already established;"
+ " extraneous LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n",
+ osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type));
return -EINVAL;
}
- conn->complete_layer3_type = COMPLETE_LAYER3_LU;
- ran_conn_update_id_from_mi(conn, lu->mi, lu->mi_len);
+ msc_a->complete_layer3_type = COMPLETE_LAYER3_LU;
+ msub_update_id_from_mi(msc_a->c.msub, lu->mi, lu->mi_len);
- LOG_RAN_CONN_CAT(conn, DMM, LOGL_DEBUG, "LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n",
- osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n",
+ osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type));
osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
switch (lu->type) {
case GSM48_LUPD_NORMAL:
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]);
vlr_lu_type = VLR_LU_TYPE_REGULAR;
break;
case GSM48_LUPD_IMSI_ATT:
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]);
vlr_lu_type = VLR_LU_TYPE_IMSI_ATTACH;
break;
case GSM48_LUPD_PERIODIC:
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]);
vlr_lu_type = VLR_LU_TYPE_PERIODIC;
break;
}
@@ -415,30 +370,33 @@ int mm_rx_loc_upd_req(struct ran_conn *conn, struct msgb *msg)
imsi = NULL;
break;
default:
- LOG_RAN_CONN_CAT(conn, DMM, LOGL_ERROR, "unknown mobile identity type\n");
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "unknown mobile identity type\n");
tmsi = GSM_RESERVED_TMSI;
imsi = NULL;
break;
}
gsm48_decode_lai2(&lu->lai, &old_lai);
- new_lai.plmn = conn->network->plmn;
- new_lai.lac = conn->lac;
- LOG_RAN_CONN_CAT(conn, DMM, LOGL_DEBUG, "LU/new-LAC: %u/%u\n", old_lai.lac, new_lai.lac);
-
- is_utran = (conn->via_ran == OSMO_RAT_UTRAN_IU);
- lu_fsm = vlr_loc_update(conn->fi,
- RAN_CONN_E_ACCEPTED, RAN_CONN_E_CN_CLOSE, NULL,
- net->vlr, conn, vlr_lu_type, tmsi, imsi,
- &old_lai, &new_lai,
- is_utran || conn->network->authentication_required,
- is_utran || conn->network->a5_encryption_mask > 0x01,
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "USIM: old LAI: %s\n", osmo_lai_name(&old_lai));
+
+ msc_a_get(msc_a, __func__);
+ msc_a_get(msc_a, MSC_A_USE_LOCATION_UPDATING);
+
+ is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU);
+ lu_fsm = vlr_loc_update(msc_a->c.fi,
+ MSC_A_EV_AUTHENTICATED, MSC_A_EV_CN_CLOSE, NULL,
+ net->vlr, msc_a, vlr_lu_type, tmsi, imsi,
+ &old_lai, &msc_a->via_cell.lai,
+ is_utran || net->authentication_required,
+ is_utran || net->a5_encryption_mask > 0x01,
lu->key_seq,
- classmark1_is_r99(&lu->classmark1),
+ osmo_gsm48_classmark1_is_r99(&lu->classmark1),
is_utran,
net->vlr->cfg.assign_tmsi);
if (!lu_fsm) {
- LOG_RAN_CONN(conn, LOGL_ERROR, "Can't start LU FSM\n");
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Can't start LU FSM\n");
+ msc_a_put(msc_a, MSC_A_USE_LOCATION_UPDATING);
+ msc_a_put(msc_a, __func__);
return 0;
}
@@ -446,15 +404,22 @@ int mm_rx_loc_upd_req(struct ran_conn *conn, struct msgb *msg)
* VLR_ULA_E_UPDATE_LA, and thus we expect msc_vlr_subscr_assoc() to
* already have been called and completed. Has an error occured? */
- if (!conn->vsub || conn->vsub->lu_fsm != lu_fsm) {
- LOG_RAN_CONN(conn, LOGL_ERROR, "internal error during Location Updating attempt\n");
- return -EIO;
+ vsub = msc_a_vsub(msc_a);
+ if (!vsub) {
+ msc_a_put(msc_a, __func__);
+ return 0;
}
- conn->vsub->classmark.classmark1 = lu->classmark1;
- conn->vsub->classmark.classmark1_set = true;
+ if (vsub->lu_fsm != lu_fsm) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Internal Error during Location Updating attempt\n");
+ msc_a_release_cn(msc_a);
+ msc_a_put(msc_a, __func__);
+ return -EIO;
+ }
- ran_conn_complete_layer_3(conn);
+ vsub->classmark.classmark1 = lu->classmark1;
+ vsub->classmark.classmark1_set = true;
+ msc_a_put(msc_a, __func__);
return 0;
}
@@ -621,15 +586,15 @@ struct msgb *gsm48_create_mm_info(struct gsm_network *net)
}
/* Section 9.2.15a */
-int gsm48_tx_mm_info(struct ran_conn *conn)
+int gsm48_tx_mm_info(struct msc_a *msc_a)
{
- struct gsm_network *net = conn->network;
+ struct gsm_network *net = msc_a_net(msc_a);
struct msgb *msg;
msg = gsm48_create_mm_info(net);
- LOG_RAN_CONN(conn, LOGL_DEBUG, "Tx MM INFO\n");
- return gsm48_conn_sendmsg(msg, conn, NULL);
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Tx MM INFO\n");
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
/*! Send an Authentication Request to MS on the given RAN connection
@@ -640,7 +605,7 @@ int gsm48_tx_mm_info(struct ran_conn *conn)
* send; must be 16 bytes long, or pass NULL for plain GSM auth.
* \param[in] key_seq auth tuple's sequence number.
*/
-int gsm48_tx_mm_auth_req(struct ran_conn *conn, uint8_t *rand,
+int gsm48_tx_mm_auth_req(struct msc_a *msc_a, uint8_t *rand,
uint8_t *autn, int key_seq)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH REQ");
@@ -666,61 +631,59 @@ int gsm48_tx_mm_auth_req(struct ran_conn *conn, uint8_t *rand,
if (autn)
msgb_tlv_put(msg, GSM48_IE_AUTN, 16, autn);
- return gsm48_conn_sendmsg(msg, conn, NULL);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
/* Section 9.2.1 */
-int gsm48_tx_mm_auth_rej(struct ran_conn *conn)
+int gsm48_tx_mm_auth_rej(struct msc_a *msc_a)
{
- DEBUGP(DMM, "-> AUTH REJECT\n");
- return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "-> AUTH REJECT\n");
+ return gsm48_tx_simple(msc_a, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
}
-static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref);
-static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum gsm48_reject_value result);
+static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type,
+ enum gsm48_reject_value cause);
-static int cm_serv_reuse_conn(struct ran_conn *conn, const uint8_t *mi_lv)
+static int cm_serv_reuse_conn(struct msc_a *msc_a, const uint8_t *mi_lv, enum osmo_cm_service_type cm_serv_type)
{
uint8_t mi_type;
char mi_string[GSM48_MI_SIZE];
uint32_t tmsi;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
gsm48_mi_to_string(mi_string, sizeof(mi_string), mi_lv+1, mi_lv[0]);
mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
switch (mi_type) {
case GSM_MI_TYPE_IMSI:
- if (vlr_subscr_matches_imsi(conn->vsub, mi_string))
+ if (vlr_subscr_matches_imsi(vsub, mi_string))
goto accept_reuse;
break;
case GSM_MI_TYPE_TMSI:
tmsi = osmo_load32be(mi_lv+2);
- if (vlr_subscr_matches_tmsi(conn->vsub, tmsi))
+ if (vlr_subscr_matches_tmsi(vsub, tmsi))
goto accept_reuse;
break;
case GSM_MI_TYPE_IMEI:
- if (vlr_subscr_matches_imei(conn->vsub, mi_string))
+ if (vlr_subscr_matches_imei(vsub, mi_string))
goto accept_reuse;
break;
default:
break;
}
- LOGP(DMM, LOGL_ERROR, "%s: CM Service Request with mismatching mobile identity: %s %s\n",
- vlr_subscr_name(conn->vsub), gsm48_mi_type_name(mi_type), mi_string);
- msc_vlr_tx_cm_serv_rej(conn, GSM48_REJECT_ILLEGAL_MS);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "CM Service Request with mismatching mobile identity: %s %s\n",
+ gsm48_mi_type_name(mi_type), mi_string);
+ msc_vlr_tx_cm_serv_rej(msc_a, cm_serv_type, GSM48_REJECT_ILLEGAL_MS);
return -EINVAL;
accept_reuse:
- DEBUGP(DMM, "%s: re-using already accepted connection\n",
- vlr_subscr_name(conn->vsub));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "re-using already accepted connection\n");
- if (!conn->received_cm_service_request) {
- conn->received_cm_service_request = true;
- ran_conn_get(conn, RAN_CONN_USE_CM_SERVICE);
- }
- ran_conn_update_id(conn);
- return conn->network->vlr->ops.tx_cm_serv_acc(conn);
+ msc_a_get(msc_a, msc_a_cm_service_type_to_use(cm_serv_type));
+ msub_update_id(msc_a->c.msub);
+ return net->vlr->ops.tx_cm_serv_acc(msc_a, cm_serv_type);
}
/*
@@ -734,50 +697,45 @@ accept_reuse:
*
* Keep this function non-static for direct invocation by unit tests.
*/
-int gsm48_rx_mm_serv_req(struct ran_conn *conn, struct msgb *msg)
+int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg)
{
- struct gsm_network *net = conn->network;
+ struct gsm_network *net = msc_a_net(msc_a);
uint8_t mi_type;
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_service_request *req =
(struct gsm48_service_request *)gh->data;
/* unfortunately in Phase1 the classmark2 length is variable */
uint8_t classmark2_len = gh->data[1];
- uint8_t *classmark2 = gh->data+2;
- uint8_t *mi_p = classmark2 + classmark2_len;
+ uint8_t *classmark2_buf = gh->data+2;
+ struct gsm48_classmark2 *cm2 = (void*)classmark2_buf;
+ uint8_t *mi_p = classmark2_buf + classmark2_len;
uint8_t mi_len = *mi_p;
uint8_t *mi = mi_p + 1;
- struct osmo_location_area_id lai;
bool is_utran;
-
- lai.plmn = conn->network->plmn;
- lai.lac = conn->lac;
+ struct vlr_subscr *vsub;
if (msg->data_len < sizeof(struct gsm48_service_request*)) {
- LOG_RAN_CONN(conn, LOGL_ERROR, "Rx CM SERVICE REQUEST: wrong message size (%u < %zu)\n",
- msg->data_len, sizeof(struct gsm48_service_request*));
- return msc_gsm48_tx_mm_serv_rej(conn,
- GSM48_REJECT_INCORRECT_MESSAGE);
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx CM SERVICE REQUEST: wrong message size (%u < %zu)\n",
+ msg->data_len, sizeof(struct gsm48_service_request*));
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE);
}
if (msg->data_len < req->mi_len + 6) {
- LOG_RAN_CONN(conn, LOGL_ERROR, "Rx CM SERVICE REQUEST: message does not fit in packet\n");
- return msc_gsm48_tx_mm_serv_rej(conn,
- GSM48_REJECT_INCORRECT_MESSAGE);
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx CM SERVICE REQUEST: message does not fit in packet\n");
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE);
}
- if (ran_conn_is_establishing_auth_ciph(conn)) {
- LOG_RAN_CONN(conn, LOGL_ERROR,
- "Cannot accept CM Service Request, conn already busy establishing authenticity\n");
- msc_vlr_tx_cm_serv_rej(conn, GSM48_REJECT_CONGESTION);
- return -EINVAL;
+ if (msc_a_is_establishing_auth_ciph(msc_a)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Cannot accept CM Service Request, conn already busy establishing authenticity\n");
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_CONGESTION);
/* or should we accept and note down the service request anyway? */
}
- conn->complete_layer3_type = COMPLETE_LAYER3_CM_SERVICE_REQ;
- ran_conn_update_id_from_mi(conn, mi, mi_len);
- LOG_RAN_CONN_CAT(conn, DMM, LOGL_DEBUG, "Rx CM SERVICE REQUEST cm_service_type=0x%02x\n",
- req->cm_service_type);
+ msc_a->complete_layer3_type = COMPLETE_LAYER3_CM_SERVICE_REQ;
+ msub_update_id_from_mi(msc_a->c.msub, mi, mi_len);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "Rx CM SERVICE REQUEST cm_service_type=%s\n",
+ osmo_cm_service_type_name(req->cm_service_type));
mi_type = (mi && mi_len) ? (mi[0] & GSM_MI_TYPE_MASK) : GSM_MI_TYPE_NONE;
switch (mi_type) {
@@ -788,59 +746,53 @@ int gsm48_rx_mm_serv_req(struct ran_conn *conn, struct msgb *msg)
case GSM_MI_TYPE_IMEI:
if (req->cm_service_type == GSM48_CMSERV_EMERGENCY) {
/* We don't do emergency calls by IMEI */
- LOG_RAN_CONN(conn, LOGL_NOTICE, "Tx CM SERVICE REQUEST REJECT\n");
- return msc_gsm48_tx_mm_serv_rej(conn, GSM48_REJECT_IMEI_NOT_ACCEPTED);
+ LOG_MSC_A(msc_a, LOGL_NOTICE, "Tx CM SERVICE REQUEST REJECT\n");
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_IMEI_NOT_ACCEPTED);
}
/* fall-through for non-emergency setup */
default:
- LOG_RAN_CONN(conn, LOGL_ERROR, "MI type is not expected: %s\n", gsm48_mi_type_name(mi_type));
- return msc_gsm48_tx_mm_serv_rej(conn,
- GSM48_REJECT_INCORRECT_MESSAGE);
+ LOG_MSC_A(msc_a, LOGL_ERROR, "MI type is not expected: %s\n", gsm48_mi_type_name(mi_type));
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE);
}
- switch (req->cm_service_type) {
- case GSM48_CMSERV_MO_CALL_PACKET:
- case GSM48_CMSERV_EMERGENCY:
- case GSM48_CMSERV_SMS:
- case GSM48_CMSERV_SUP_SERV:
- /* continue below */
- break;
- default:
- return msc_gsm48_tx_mm_serv_rej(conn, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED);
- }
+ if (!msc_a_cm_service_type_to_use(req->cm_service_type))
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED);
- if (ran_conn_is_accepted(conn))
- return cm_serv_reuse_conn(conn, mi_p);
+ if (msc_a_is_accepted(msc_a))
+ return cm_serv_reuse_conn(msc_a, mi_p, req->cm_service_type);
osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, mi_p);
- is_utran = (conn->via_ran == OSMO_RAT_UTRAN_IU);
- vlr_proc_acc_req(conn->fi,
- RAN_CONN_E_ACCEPTED, RAN_CONN_E_CN_CLOSE, NULL,
- net->vlr, conn,
- VLR_PR_ARQ_T_CM_SERV_REQ, mi-1, &lai,
- is_utran || conn->network->authentication_required,
- is_utran || conn->network->a5_encryption_mask > 0x01,
+ msc_a_get(msc_a, msc_a_cm_service_type_to_use(req->cm_service_type));
+
+ is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU);
+ vlr_proc_acc_req(msc_a->c.fi,
+ MSC_A_EV_AUTHENTICATED, MSC_A_EV_CN_CLOSE, NULL,
+ net->vlr, msc_a,
+ VLR_PR_ARQ_T_CM_SERV_REQ,
+ req->cm_service_type,
+ mi-1, &msc_a->via_cell.lai,
+ is_utran || net->authentication_required,
+ is_utran || net->a5_encryption_mask > 0x01,
req->cipher_key_seq,
- classmark2_is_r99(classmark2, classmark2_len),
+ osmo_gsm48_classmark2_is_r99(cm2, classmark2_len),
is_utran);
/* From vlr_proc_acc_req() we expect an implicit dispatch of PR_ARQ_E_START we expect
* msc_vlr_subscr_assoc() to already have been called and completed. Has an error occured? */
- if (!conn->vsub) {
- LOG_RAN_CONN(conn, LOGL_ERROR, "subscriber not allowed to do a CM Service Request\n");
+ vsub = msc_a_vsub(msc_a);
+ if (!vsub) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "subscriber not allowed to do a CM Service Request\n");
return -EIO;
}
- memcpy(conn->vsub->classmark.classmark2, classmark2, classmark2_len);
- conn->vsub->classmark.classmark2_len = classmark2_len;
-
- ran_conn_complete_layer_3(conn);
+ vsub->classmark.classmark2 = *cm2;
+ vsub->classmark.classmark2_len = classmark2_len;
return 0;
}
/* Receive a CM Re-establish Request */
-static int gsm48_rx_cm_reest_req(struct ran_conn *conn, struct msgb *msg)
+static int gsm48_rx_cm_reest_req(struct msc_a *msc_a, struct msgb *msg)
{
uint8_t mi_type;
char mi_string[GSM48_MI_SIZE];
@@ -856,12 +808,12 @@ static int gsm48_rx_cm_reest_req(struct ran_conn *conn, struct msgb *msg)
DEBUGP(DMM, "<- CM RE-ESTABLISH REQUEST MI(%s)=%s\n", gsm48_mi_type_name(mi_type), mi_string);
/* we don't support CM call re-establishment */
- return msc_gsm48_tx_mm_serv_rej(conn, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED);
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED);
}
-static int gsm48_rx_mm_imsi_detach_ind(struct ran_conn *conn, struct msgb *msg)
+static int gsm48_rx_mm_imsi_detach_ind(struct msc_a *msc_a, struct msgb *msg)
{
- struct gsm_network *network = conn->network;
+ struct gsm_network *net = msc_a_net(msc_a);
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_imsi_detach_ind *idi =
(struct gsm48_imsi_detach_ind *) gh->data;
@@ -873,15 +825,15 @@ static int gsm48_rx_mm_imsi_detach_ind(struct ran_conn *conn, struct msgb *msg)
DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s\n",
gsm48_mi_type_name(mi_type), mi_string);
- rate_ctr_inc(&network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]);
switch (mi_type) {
case GSM_MI_TYPE_TMSI:
- vsub = vlr_subscr_find_by_tmsi(network->vlr,
+ vsub = vlr_subscr_find_by_tmsi(net->vlr,
tmsi_from_string(mi_string), __func__);
break;
case GSM_MI_TYPE_IMSI:
- vsub = vlr_subscr_find_by_imsi(network->vlr, mi_string, __func__);
+ vsub = vlr_subscr_find_by_imsi(net->vlr, mi_string, __func__);
break;
case GSM_MI_TYPE_IMEI:
case GSM_MI_TYPE_IMEISV:
@@ -900,11 +852,11 @@ static int gsm48_rx_mm_imsi_detach_ind(struct ran_conn *conn, struct msgb *msg)
gsm48_mi_type_name(mi_type), mi_string);
} else {
LOGP(DMM, LOGL_INFO, "IMSI DETACH for %s\n", vlr_subscr_name(vsub));
+ msub_set_vsub(msc_a->c.msub, vsub);
if (vsub->cs.is_paging)
- subscr_paging_cancel(vsub, GSM_PAGING_EXPIRED);
+ paging_expired(vsub);
- /* We already got Classmark 1 during Location Updating ... but well, ok */
vsub->classmark.classmark1 = idi->classmark1;
vlr_subscr_rx_imsi_detach(vsub);
@@ -912,7 +864,7 @@ static int gsm48_rx_mm_imsi_detach_ind(struct ran_conn *conn, struct msgb *msg)
vlr_subscr_put(vsub, __func__);
}
- ran_conn_close(conn, 0);
+ msc_a_release_cn(msc_a);
return 0;
}
@@ -926,17 +878,15 @@ static int gsm48_rx_mm_status(struct msgb *msg)
}
static int parse_gsm_auth_resp(uint8_t *res, uint8_t *res_len,
- struct ran_conn *conn,
+ struct msc_a *msc_a,
struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data;
if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*ar)) {
- LOGP(DMM, LOGL_ERROR,
- "%s: MM AUTHENTICATION RESPONSE:"
- " l3 length invalid: %u\n",
- vlr_subscr_name(conn->vsub), msgb_l3len(msg));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "MM AUTHENTICATION RESPONSE: l3 length invalid: %u\n",
+ msgb_l3len(msg));
return -EINVAL;
}
@@ -946,7 +896,7 @@ static int parse_gsm_auth_resp(uint8_t *res, uint8_t *res_len,
}
static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len,
- struct ran_conn *conn,
+ struct msc_a *msc_a,
struct msgb *msg)
{
struct gsm48_hdr *gh;
@@ -956,7 +906,7 @@ static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len,
unsigned int data_len;
/* First parse the GSM part */
- if (parse_gsm_auth_resp(res, res_len, conn, msg))
+ if (parse_gsm_auth_resp(res, res_len, msc_a, msg))
return -EINVAL;
OSMO_ASSERT(*res_len == 4);
@@ -966,29 +916,23 @@ static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len,
data_len = msgb_l3len(msg) - (data - (uint8_t*)msgb_l3(msg));
if (data_len < 3) {
- LOGP(DMM, LOGL_ERROR,
- "%s: MM AUTHENTICATION RESPONSE:"
- " l3 length invalid: %u\n",
- vlr_subscr_name(conn->vsub), msgb_l3len(msg));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "MM AUTHENTICATION RESPONSE: l3 length invalid: %u\n",
+ msgb_l3len(msg));
return -EINVAL;
}
iei = data[0];
ie_len = data[1];
if (iei != GSM48_IE_AUTH_RES_EXT) {
- LOGP(DMM, LOGL_ERROR,
- "%s: MM R99 AUTHENTICATION RESPONSE:"
- " expected IEI 0x%02x, got 0x%02x\n",
- vlr_subscr_name(conn->vsub),
- GSM48_IE_AUTH_RES_EXT, iei);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "MM R99 AUTHENTICATION RESPONSE: expected IEI 0x%02x, got 0x%02x\n",
+ GSM48_IE_AUTH_RES_EXT, iei);
return -EINVAL;
}
if (ie_len > 12) {
- LOGP(DMM, LOGL_ERROR,
- "%s: MM R99 AUTHENTICATION RESPONSE:"
- " extended Auth Resp IE 0x%02x is too large: %u bytes\n",
- vlr_subscr_name(conn->vsub), GSM48_IE_AUTH_RES_EXT, ie_len);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR,
+ "MM R99 AUTHENTICATION RESPONSE: extended Auth Resp IE 0x%02x is too large: %u bytes\n",
+ GSM48_IE_AUTH_RES_EXT, ie_len);
return -EINVAL;
}
@@ -998,77 +942,72 @@ static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len,
}
/* Chapter 9.2.3: Authentication Response */
-static int gsm48_rx_mm_auth_resp(struct ran_conn *conn, struct msgb *msg)
+static int gsm48_rx_mm_auth_resp(struct msc_a *msc_a, struct msgb *msg)
{
uint8_t res[16];
uint8_t res_len;
int rc;
bool is_umts;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
- if (!conn->vsub) {
- LOGP(DMM, LOGL_ERROR,
- "MM AUTHENTICATION RESPONSE: invalid: no subscriber\n");
- ran_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
+ if (!vsub) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "MM AUTHENTICATION RESPONSE: invalid: no subscriber\n");
+ msc_a_release_mo(msc_a, GSM_CAUSE_AUTH_FAILED);
return -EINVAL;
}
is_umts = (msgb_l3len(msg) > sizeof(struct gsm48_hdr) + sizeof(struct gsm48_auth_resp));
if (is_umts)
- rc = parse_umts_auth_resp(res, &res_len, conn, msg);
+ rc = parse_umts_auth_resp(res, &res_len, msc_a, msg);
else
- rc = parse_gsm_auth_resp(res, &res_len, conn, msg);
+ rc = parse_gsm_auth_resp(res, &res_len, msc_a, msg);
if (rc) {
- LOGP(DMM, LOGL_ERROR,
- "%s: MM AUTHENTICATION RESPONSE: invalid: parsing %s AKA Auth Response"
- " failed with rc=%d; dispatching zero length SRES/RES to trigger failure\n",
- vlr_subscr_name(conn->vsub), is_umts ? "UMTS" : "GSM", rc);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR,
+ "MM AUTHENTICATION RESPONSE: invalid: parsing %s AKA Auth Response"
+ " failed with rc=%d; dispatching zero length SRES/RES to trigger failure\n",
+ is_umts ? "UMTS" : "GSM", rc);
memset(res, 0, sizeof(res));
res_len = 0;
}
- DEBUGP(DMM, "%s: MM %s AUTHENTICATION RESPONSE (%s = %s)\n",
- vlr_subscr_name(conn->vsub),
- is_umts ? "UMTS" : "GSM", is_umts ? "res" : "sres",
- osmo_hexdump_nospc(res, res_len));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "MM %s AUTHENTICATION RESPONSE (%s = %s)\n",
+ is_umts ? "UMTS" : "GSM", is_umts ? "res" : "sres",
+ osmo_hexdump_nospc(res, res_len));
- return vlr_subscr_rx_auth_resp(conn->vsub, classmark_is_r99(&conn->vsub->classmark),
- conn->via_ran == OSMO_RAT_UTRAN_IU,
+ return vlr_subscr_rx_auth_resp(vsub, osmo_gsm48_classmark_is_r99(&vsub->classmark),
+ msc_a->c.ran->type == OSMO_RAT_UTRAN_IU,
res, res_len);
}
-static int gsm48_rx_mm_auth_fail(struct ran_conn *conn, struct msgb *msg)
+static int gsm48_rx_mm_auth_fail(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t cause;
uint8_t auts_tag;
uint8_t auts_len;
uint8_t *auts;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
- if (!conn->vsub) {
- LOGP(DMM, LOGL_ERROR,
- "MM R99 AUTHENTICATION FAILURE: invalid: no subscriber\n");
- ran_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
+ if (!vsub) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "MM R99 AUTHENTICATION FAILURE: invalid: no subscriber\n");
+ msc_a_release_mo(msc_a, GSM_CAUSE_AUTH_FAILED);
return -EINVAL;
}
if (msgb_l3len(msg) < sizeof(*gh) + 1) {
- LOGP(DMM, LOGL_ERROR,
- "%s: MM R99 AUTHENTICATION FAILURE:"
- " l3 length invalid: %u\n",
- vlr_subscr_name(conn->vsub), msgb_l3len(msg));
- ran_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "MM R99 AUTHENTICATION FAILURE: l3 length invalid: %u\n",
+ msgb_l3len(msg));
+ msc_a_release_mo(msc_a, GSM_CAUSE_AUTH_FAILED);
return -EINVAL;
}
cause = gh->data[0];
if (cause != GSM48_REJECT_SYNCH_FAILURE) {
- LOGP(DMM, LOGL_INFO,
- "%s: MM R99 AUTHENTICATION FAILURE: cause 0x%0x\n",
- vlr_subscr_name(conn->vsub), cause);
- vlr_subscr_rx_auth_fail(conn->vsub, NULL);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_INFO, "MM R99 AUTHENTICATION FAILURE: cause 0x%0x\n", cause);
+ vlr_subscr_rx_auth_fail(vsub, NULL);
return 0;
}
@@ -1077,11 +1016,9 @@ static int gsm48_rx_mm_auth_fail(struct ran_conn *conn, struct msgb *msg)
* TLV with 14 bytes of AUTS. */
if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2) {
- LOGP(DMM, LOGL_INFO,
- "%s: MM R99 AUTHENTICATION FAILURE:"
- " invalid Synch Failure: missing AUTS IE\n",
- vlr_subscr_name(conn->vsub));
- ran_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_INFO,
+ "MM R99 AUTHENTICATION FAILURE: invalid Synch Failure: missing AUTS IE\n");
+ msc_a_release_mo(msc_a, GSM_CAUSE_AUTH_FAILED);
return -EINVAL;
}
@@ -1091,80 +1028,76 @@ static int gsm48_rx_mm_auth_fail(struct ran_conn *conn, struct msgb *msg)
if (auts_tag != GSM48_IE_AUTS
|| auts_len != 14) {
- LOGP(DMM, LOGL_INFO,
- "%s: MM R99 AUTHENTICATION FAILURE:"
- " invalid Synch Failure:"
- " expected AUTS IE 0x%02x of 14 bytes,"
- " got IE 0x%02x of %u bytes\n",
- vlr_subscr_name(conn->vsub),
- GSM48_IE_AUTS, auts_tag, auts_len);
- ran_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_INFO,
+ "MM R99 AUTHENTICATION FAILURE: invalid Synch Failure:"
+ " expected AUTS IE 0x%02x of 14 bytes,"
+ " got IE 0x%02x of %u bytes\n",
+ GSM48_IE_AUTS, auts_tag, auts_len);
+ msc_a_release_mo(msc_a, GSM_CAUSE_AUTH_FAILED);
return -EINVAL;
}
if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2 + auts_len) {
- LOGP(DMM, LOGL_INFO,
- "%s: MM R99 AUTHENTICATION FAILURE:"
- " invalid Synch Failure msg: message truncated (%u)\n",
- vlr_subscr_name(conn->vsub), msgb_l3len(msg));
- ran_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_INFO,
+ "MM R99 AUTHENTICATION FAILURE: invalid Synch Failure msg: message truncated (%u)\n",
+ msgb_l3len(msg));
+ msc_a_release_mo(msc_a, GSM_CAUSE_AUTH_FAILED);
return -EINVAL;
}
/* We have an AUTS IE with exactly 14 bytes of AUTS and the msgb is
* large enough. */
- DEBUGP(DMM, "%s: MM R99 AUTHENTICATION SYNCH (AUTS = %s)\n",
- vlr_subscr_name(conn->vsub), osmo_hexdump_nospc(auts, 14));
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "MM R99 AUTHENTICATION SYNCH (AUTS = %s)\n",
+ osmo_hexdump_nospc(auts, 14));
- return vlr_subscr_rx_auth_fail(conn->vsub, auts);
+ return vlr_subscr_rx_auth_fail(vsub, auts);
}
-static int gsm48_rx_mm_tmsi_reall_compl(struct ran_conn *conn)
+static int gsm48_rx_mm_tmsi_reall_compl(struct msc_a *msc_a)
{
- DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
- vlr_subscr_name(conn->vsub));
- if (!conn->vsub) {
- LOGP(DMM, LOGL_ERROR,
- "Rx MM TMSI Reallocation Complete: invalid: no subscriber\n");
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ if (!vsub) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "Rx MM TMSI Reallocation Complete: invalid: no subscriber\n");
return -EINVAL;
}
- return vlr_subscr_rx_tmsi_reall_compl(conn->vsub);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "TMSI Reallocation Completed\n");
+ return vlr_subscr_rx_tmsi_reall_compl(vsub);
}
/* Receive a GSM 04.08 Mobility Management (MM) message */
-static int gsm0408_rcv_mm(struct ran_conn *conn, struct msgb *msg)
+int gsm0408_rcv_mm(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
int rc = 0;
switch (gsm48_hdr_msg_type(gh)) {
case GSM48_MT_MM_LOC_UPD_REQUEST:
- rc = mm_rx_loc_upd_req(conn, msg);
+ rc = mm_rx_loc_upd_req(msc_a, msg);
break;
case GSM48_MT_MM_ID_RESP:
- rc = mm_rx_id_resp(conn, msg);
+ rc = mm_rx_id_resp(msc_a, msg);
break;
case GSM48_MT_MM_CM_SERV_REQ:
- rc = gsm48_rx_mm_serv_req(conn, msg);
+ rc = gsm48_rx_mm_serv_req(msc_a, msg);
break;
case GSM48_MT_MM_STATUS:
rc = gsm48_rx_mm_status(msg);
break;
case GSM48_MT_MM_TMSI_REALL_COMPL:
- rc = gsm48_rx_mm_tmsi_reall_compl(conn);
+ rc = gsm48_rx_mm_tmsi_reall_compl(msc_a);
break;
case GSM48_MT_MM_IMSI_DETACH_IND:
- rc = gsm48_rx_mm_imsi_detach_ind(conn, msg);
+ rc = gsm48_rx_mm_imsi_detach_ind(msc_a, msg);
break;
case GSM48_MT_MM_CM_REEST_REQ:
- rc = gsm48_rx_cm_reest_req(conn, msg);
+ rc = gsm48_rx_cm_reest_req(msc_a, msg);
break;
case GSM48_MT_MM_AUTH_RESP:
- rc = gsm48_rx_mm_auth_resp(conn, msg);
+ rc = gsm48_rx_mm_auth_resp(msc_a, msg);
break;
case GSM48_MT_MM_AUTH_FAIL:
- rc = gsm48_rx_mm_auth_fail(conn, msg);
+ rc = gsm48_rx_mm_auth_fail(msc_a, msg);
break;
default:
LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n",
@@ -1176,62 +1109,85 @@ static int gsm0408_rcv_mm(struct ran_conn *conn, struct msgb *msg)
}
/* Receive a PAGING RESPONSE message from the MS */
-static int gsm48_rx_rr_pag_resp(struct ran_conn *conn, struct msgb *msg)
+static int gsm48_rx_rr_pag_resp(struct msc_a *msc_a, struct msgb *msg)
{
- struct gsm_network *net = conn->network;
+ struct gsm_network *net = msc_a_net(msc_a);
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_pag_resp *pr =
(struct gsm48_pag_resp *)gh->data;
uint8_t classmark2_len = gh->data[1];
- uint8_t *classmark2 = gh->data+2;
- uint8_t *mi_lv = classmark2 + classmark2_len;
- struct osmo_location_area_id lai;
+ uint8_t *classmark2_buf = gh->data+2;
+ struct gsm48_classmark2 *cm2 = (void*)classmark2_buf;
+ uint8_t *mi_lv = classmark2_buf + classmark2_len;
bool is_utran;
+ struct vlr_subscr *vsub;
- lai.plmn = conn->network->plmn;
- lai.lac = conn->lac;
-
- if (ran_conn_is_establishing_auth_ciph(conn)) {
- LOGP(DMM, LOGL_ERROR,
- "Ignoring Paging Response, conn already busy establishing authenticity\n");
+ if (msc_a_is_establishing_auth_ciph(msc_a)) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR,
+ "Ignoring Paging Response, conn already busy establishing authenticity\n");
return 0;
}
- if (ran_conn_is_accepted(conn)) {
- LOGP(DMM, LOGL_ERROR, "Ignoring Paging Response, conn already established\n");
+ if (msc_a_is_accepted(msc_a)) {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "Ignoring Paging Response, conn already established\n");
return 0;
}
- conn->complete_layer3_type = COMPLETE_LAYER3_PAGING_RESP;
- ran_conn_update_id_from_mi(conn, mi_lv + 1, *mi_lv);
- LOG_RAN_CONN_CAT(conn, DRR, LOGL_DEBUG, "PAGING RESPONSE\n");
-
- is_utran = (conn->via_ran == OSMO_RAT_UTRAN_IU);
- vlr_proc_acc_req(conn->fi,
- RAN_CONN_E_ACCEPTED, RAN_CONN_E_CN_CLOSE, NULL,
- net->vlr, conn,
- VLR_PR_ARQ_T_PAGING_RESP, mi_lv, &lai,
- is_utran || conn->network->authentication_required,
- is_utran || conn->network->a5_encryption_mask > 0x01,
+ msc_a->complete_layer3_type = COMPLETE_LAYER3_PAGING_RESP;
+ msub_update_id_from_mi(msc_a->c.msub, mi_lv + 1, *mi_lv);
+ LOG_MSC_A_CAT(msc_a, DRR, LOGL_DEBUG, "Rx PAGING RESPONSE\n");
+
+ msc_a_get(msc_a, MSC_A_USE_PAGING_RESPONSE);
+
+ is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU);
+ vlr_proc_acc_req(msc_a->c.fi,
+ MSC_A_EV_AUTHENTICATED, MSC_A_EV_CN_CLOSE, NULL,
+ net->vlr, msc_a,
+ VLR_PR_ARQ_T_PAGING_RESP, 0, mi_lv, &msc_a->via_cell.lai,
+ is_utran || net->authentication_required,
+ is_utran || net->a5_encryption_mask > 0x01,
pr->key_seq,
- classmark2_is_r99(classmark2, classmark2_len),
+ osmo_gsm48_classmark2_is_r99(cm2, classmark2_len),
is_utran);
/* From vlr_proc_acc_req() we expect an implicit dispatch of PR_ARQ_E_START we expect
* msc_vlr_subscr_assoc() to already have been called and completed. Has an error occured? */
- if (!conn->vsub) {
- LOG_RAN_CONN(conn, LOGL_ERROR, "subscriber not allowed to do a Paging Response\n");
+ vsub = msc_a_vsub(msc_a);
+ if (!vsub) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "subscriber not allowed to do a Paging Response\n");
+ msc_a_put(msc_a, MSC_A_USE_PAGING_RESPONSE);
return -EIO;
}
- memcpy(conn->vsub->classmark.classmark2, classmark2, classmark2_len);
- conn->vsub->classmark.classmark2_len = classmark2_len;
-
- ran_conn_complete_layer_3(conn);
+ vsub->classmark.classmark2 = *cm2;
+ vsub->classmark.classmark2_len = classmark2_len;
return 0;
}
-static int gsm48_rx_rr_app_info(struct ran_conn *conn, struct msgb *msg)
+static int gsm48_rx_rr_ciphering_mode_complete(struct msc_a *msc_a, struct msgb *msg)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct tlv_p_entry *mi;
+
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ mi = TLVP_GET(&tp, GSM48_IE_MOBILE_ID);
+
+ if (!mi)
+ return 0;
+
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Ciphering Mode Complete contains Mobile Identity: %s\n",
+ osmo_mi_name(mi->val, mi->len));
+
+ if (!vsub)
+ return 0;
+
+ return vlr_subscr_rx_id_resp(vsub, mi->val, mi->len);
+}
+
+static int gsm48_rx_rr_app_info(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t apdu_id_flags;
@@ -1254,28 +1210,31 @@ static int gsm48_rx_rr_app_info(struct ran_conn *conn, struct msgb *msg)
}
/* Receive a GSM 04.08 Radio Resource (RR) message */
-static int gsm0408_rcv_rr(struct ran_conn *conn, struct msgb *msg)
+int gsm0408_rcv_rr(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
int rc = 0;
switch (gh->msg_type) {
case GSM48_MT_RR_PAG_RESP:
- rc = gsm48_rx_rr_pag_resp(conn, msg);
+ rc = gsm48_rx_rr_pag_resp(msc_a, msg);
+ break;
+ case GSM48_MT_RR_CIPH_M_COMPL:
+ rc = gsm48_rx_rr_ciphering_mode_complete(msc_a, msg);
break;
case GSM48_MT_RR_APP_INFO:
- rc = gsm48_rx_rr_app_info(conn, msg);
+ rc = gsm48_rx_rr_app_info(msc_a, msg);
break;
default:
- LOGP(DRR, LOGL_NOTICE, "MSC: Unimplemented %s GSM 04.08 RR "
- "message\n", gsm48_rr_msg_name(gh->msg_type));
+ LOG_MSC_A_CAT(msc_a, DRR, LOGL_NOTICE, "MSC: Unimplemented %s GSM 04.08 RR "
+ "message\n", gsm48_rr_msg_name(gh->msg_type));
break;
}
return rc;
}
-int gsm48_send_rr_app_info(struct ran_conn *conn, uint8_t apdu_id,
+int gsm48_send_rr_app_info(struct msc_a *msc_a, uint8_t apdu_id,
uint8_t apdu_len, const uint8_t *apdu)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 APP INF");
@@ -1291,245 +1250,10 @@ int gsm48_send_rr_app_info(struct ran_conn *conn, uint8_t apdu_id,
gh->data[1] = apdu_len;
memcpy(gh->data+2, apdu, apdu_len);
- return gsm48_conn_sendmsg(msg, conn, NULL);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
-static bool msg_is_initially_permitted(const struct gsm48_hdr *hdr)
-{
- uint8_t pdisc = gsm48_hdr_pdisc(hdr);
- uint8_t msg_type = gsm48_hdr_msg_type(hdr);
-
- switch (pdisc) {
- case GSM48_PDISC_MM:
- switch (msg_type) {
- case GSM48_MT_MM_LOC_UPD_REQUEST:
- case GSM48_MT_MM_CM_SERV_REQ:
- case GSM48_MT_MM_CM_REEST_REQ:
- case GSM48_MT_MM_AUTH_RESP:
- case GSM48_MT_MM_AUTH_FAIL:
- case GSM48_MT_MM_ID_RESP:
- case GSM48_MT_MM_TMSI_REALL_COMPL:
- case GSM48_MT_MM_IMSI_DETACH_IND:
- return true;
- default:
- break;
- }
- break;
- case GSM48_PDISC_RR:
- switch (msg_type) {
- /* GSM48_MT_RR_CIPH_M_COMPL is actually handled in bssmap_rx_ciph_compl() and gets redirected in the
- * BSSAP layer to ran_conn_cipher_mode_compl() (before this here is reached) */
- case GSM48_MT_RR_PAG_RESP:
- return true;
- default:
- break;
- }
- break;
- default:
- break;
- }
-
- return false;
-}
-
-void cm_service_request_concludes(struct ran_conn *conn,
- struct msgb *msg)
-{
-
- /* If a CM Service Request was received before, this is the request the
- * conn was opened for. No need to wait for further messages. */
-
- if (!conn->received_cm_service_request)
- return;
-
- if (log_check_level(DMM, LOGL_DEBUG)) {
- struct gsm48_hdr *gh = msgb_l3(msg);
- uint8_t pdisc = gsm48_hdr_pdisc(gh);
- uint8_t msg_type = gsm48_hdr_msg_type(gh);
-
- DEBUGP(DMM, "%s: rx msg %s:"
- " received_cm_service_request changes to false\n",
- vlr_subscr_name(conn->vsub),
- gsm48_pdisc_msgtype_name(pdisc, msg_type));
- }
- conn->received_cm_service_request = false;
- ran_conn_put(conn, RAN_CONN_USE_CM_SERVICE);
-}
-
-/* TS 24.007 11.2.3.2.3 Message Type Octet / Duplicate Detection */
-int gsm0407_pdisc_ctr_bin(uint8_t pdisc)
-{
- switch (pdisc) {
- case GSM48_PDISC_MM:
- case GSM48_PDISC_CC:
- case GSM48_PDISC_NC_SS:
- return 0;
- case GSM48_PDISC_GROUP_CC:
- return 1;
- case GSM48_PDISC_BCAST_CC:
- return 2;
- case GSM48_PDISC_LOC:
- return 3;
- default:
- return -1;
- }
-}
-
-/* extract the N(SD) and return the modulo value for a R98 message */
-static uint8_t gsm0407_determine_nsd_ret_modulo_r99(uint8_t pdisc, uint8_t msg_type, uint8_t *n_sd)
-{
- switch (pdisc) {
- case GSM48_PDISC_MM:
- case GSM48_PDISC_CC:
- case GSM48_PDISC_NC_SS:
- *n_sd = (msg_type >> 6) & 0x3;
- return 4;
- case GSM48_PDISC_GROUP_CC:
- case GSM48_PDISC_BCAST_CC:
- case GSM48_PDISC_LOC:
- *n_sd = (msg_type >> 6) & 0x1;
- return 2;
- default:
- /* no sequence number, we cannot detect dups */
- return 0;
- }
-}
-
-/* extract the N(SD) and return the modulo value for a R99 message */
-static uint8_t gsm0407_determine_nsd_ret_modulo_r98(uint8_t pdisc, uint8_t msg_type, uint8_t *n_sd)
-{
- switch (pdisc) {
- case GSM48_PDISC_MM:
- case GSM48_PDISC_CC:
- case GSM48_PDISC_NC_SS:
- case GSM48_PDISC_GROUP_CC:
- case GSM48_PDISC_BCAST_CC:
- case GSM48_PDISC_LOC:
- *n_sd = (msg_type >> 6) & 0x1;
- return 2;
- default:
- /* no sequence number, we cannot detect dups */
- return 0;
- }
-}
-
-/* TS 24.007 11.2.3.2 Message Type Octet / Duplicate Detection */
-static bool gsm0407_is_duplicate(struct ran_conn *conn, struct msgb *msg)
-{
- struct gsm48_hdr *gh;
- uint8_t pdisc;
- uint8_t n_sd, modulo;
- int bin;
-
- gh = msgb_l3(msg);
- pdisc = gsm48_hdr_pdisc(gh);
-
- if (conn->vsub && classmark_is_r99(&conn->vsub->classmark)) {
- modulo = gsm0407_determine_nsd_ret_modulo_r99(pdisc, gh->msg_type, &n_sd);
- } else { /* pre R99 */
- modulo = gsm0407_determine_nsd_ret_modulo_r98(pdisc, gh->msg_type, &n_sd);
- }
- if (modulo == 0)
- return false;
- bin = gsm0407_pdisc_ctr_bin(pdisc);
- if (bin < 0)
- return false;
-
- OSMO_ASSERT(bin < ARRAY_SIZE(conn->n_sd_next));
- if (n_sd != conn->n_sd_next[bin]) {
- /* not what we expected: duplicate */
- return true;
- } else {
- /* as expected: no dup; update expected counter for next message */
- conn->n_sd_next[bin] = (n_sd + 1) % modulo;
- return false;
- }
-}
-
-extern int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg);
-
-/* Main entry point for GSM 04.08/44.008 Layer 3 data (e.g. from the BSC). */
-int gsm0408_dispatch(struct ran_conn *conn, struct msgb *msg)
-{
- struct gsm48_hdr *gh;
- uint8_t pdisc;
- int rc = 0;
-
- OSMO_ASSERT(msg->l3h);
- OSMO_ASSERT(conn);
- OSMO_ASSERT(msg);
-
- gh = msgb_l3(msg);
- pdisc = gsm48_hdr_pdisc(gh);
-
- if (gsm0407_is_duplicate(conn, msg)) {
- LOGP(DRLL, LOGL_NOTICE, "%s: Discarding duplicate L3 message\n",
- (conn && conn->vsub) ? vlr_subscr_name(conn->vsub) : "UNKNOWN");
- return 0;
- }
-
- LOGP(DRLL, LOGL_DEBUG, "Dispatching 04.08 message %s (0x%x:0x%x)\n",
- gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh)),
- pdisc, gsm48_hdr_msg_type(gh));
-
- if (!ran_conn_is_accepted(conn)
- && !msg_is_initially_permitted(gh)) {
- LOGP(DRLL, LOGL_ERROR,
- "subscr %s: Message not permitted for initial conn: %s\n",
- vlr_subscr_name(conn->vsub),
- gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh)));
- return -EACCES;
- }
-
- if (conn->vsub && conn->vsub->cs.attached_via_ran != conn->via_ran) {
- LOGP(DMM, LOGL_ERROR,
- "%s: Illegal situation: RAN type mismatch:"
- " attached via %s, received message via %s\n",
- vlr_subscr_name(conn->vsub),
- osmo_rat_type_name(conn->vsub->cs.attached_via_ran),
- osmo_rat_type_name(conn->via_ran));
- return -EACCES;
- }
-
-#if 0
- if (silent_call_reroute(conn, msg))
- return silent_call_rx(conn, msg);
-#endif
-
- switch (pdisc) {
- case GSM48_PDISC_CC:
- rc = gsm0408_rcv_cc(conn, msg);
- break;
- case GSM48_PDISC_MM:
- rc = gsm0408_rcv_mm(conn, msg);
- break;
- case GSM48_PDISC_RR:
- rc = gsm0408_rcv_rr(conn, msg);
- break;
- case GSM48_PDISC_SMS:
- rc = gsm0411_rcv_sms(conn, msg);
- break;
- case GSM48_PDISC_MM_GPRS:
- case GSM48_PDISC_SM_GPRS:
- LOGP(DRLL, LOGL_NOTICE, "Unimplemented "
- "GSM 04.08 discriminator 0x%02x\n", pdisc);
- rc = -ENOTSUP;
- break;
- case GSM48_PDISC_NC_SS:
- rc = gsm0911_rcv_nc_ss(conn, msg);
- break;
- case GSM48_PDISC_TEST:
- rc = gsm0414_rcv_test(conn, msg);
- break;
- default:
- LOGP(DRLL, LOGL_NOTICE, "Unknown "
- "GSM 04.08 discriminator 0x%02x\n", pdisc);
- rc = -EINVAL;
- break;
- }
-
- return rc;
-}
+extern int gsm0408_rcv_cc(struct msc_a *msc_a, struct msgb *msg);
/***********************************************************************
* VLR integration
@@ -1539,8 +1263,8 @@ int gsm0408_dispatch(struct ran_conn *conn, struct msgb *msg)
static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct vlr_auth_tuple *at,
bool send_autn)
{
- struct ran_conn *conn = msc_conn_ref;
- return gsm48_tx_mm_auth_req(conn, at->vec.rand,
+ struct msc_a *msc_a = msc_conn_ref;
+ return gsm48_tx_mm_auth_req(msc_a, at->vec.rand,
send_autn? at->vec.autn : NULL,
at->key_seq);
}
@@ -1548,310 +1272,104 @@ static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct vlr_auth_tuple *at,
/* VLR asks us to send an authentication reject */
static int msc_vlr_tx_auth_rej(void *msc_conn_ref)
{
- struct ran_conn *conn = msc_conn_ref;
- return gsm48_tx_mm_auth_rej(conn);
+ struct msc_a *msc_a = msc_conn_ref;
+ return gsm48_tx_mm_auth_rej(msc_a);
}
/* VLR asks us to transmit an Identity Request of given type */
static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type)
{
- struct ran_conn *conn = msc_conn_ref;
- return mm_tx_identity_req(conn, mi_type);
+ struct msc_a *msc_a = msc_conn_ref;
+ return mm_tx_identity_req(msc_a, mi_type);
}
/* VLR asks us to transmit a Location Update Accept */
static int msc_vlr_tx_lu_acc(void *msc_conn_ref, uint32_t send_tmsi)
{
- struct ran_conn *conn = msc_conn_ref;
- return gsm0408_loc_upd_acc(conn, send_tmsi);
+ struct msc_a *msc_a = msc_conn_ref;
+ return gsm0408_loc_upd_acc(msc_a, send_tmsi);
}
/* VLR asks us to transmit a Location Update Reject */
static int msc_vlr_tx_lu_rej(void *msc_conn_ref, enum gsm48_reject_value cause)
{
- struct ran_conn *conn = msc_conn_ref;
- return gsm0408_loc_upd_rej(conn, cause);
+ struct msc_a *msc_a = msc_conn_ref;
+ return gsm0408_loc_upd_rej(msc_a, cause);
}
/* VLR asks us to transmit a CM Service Accept */
-static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref)
+int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type)
{
- struct ran_conn *conn = msc_conn_ref;
- return msc_gsm48_tx_mm_serv_ack(conn);
+ struct msc_a *msc_a = msc_conn_ref;
+ return msc_gsm48_tx_mm_serv_ack(msc_a);
}
static int msc_vlr_tx_common_id(void *msc_conn_ref)
{
- struct ran_conn *conn = msc_conn_ref;
- return msc_tx_common_id(conn);
+ struct msc_a *msc_a = msc_conn_ref;
+ struct ran_msg msg = {
+ .msg_type = RAN_MSG_COMMON_ID,
+ .common_id = {
+ .imsi = msc_a_vsub(msc_a)->imsi,
+ },
+ };
+ return msc_a_ran_down(msc_a, MSC_ROLE_I, &msg);
}
/* VLR asks us to transmit MM info. */
static int msc_vlr_tx_mm_info(void *msc_conn_ref)
{
- struct ran_conn *conn = msc_conn_ref;
- if (!conn->network->send_mm_info)
+ struct msc_a *msc_a = msc_conn_ref;
+ struct gsm_network *net = msc_a_net(msc_a);
+ if (!net->send_mm_info)
return 0;
- return gsm48_tx_mm_info(conn);
+ return gsm48_tx_mm_info(msc_a);
}
/* VLR asks us to transmit a CM Service Reject */
-static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum gsm48_reject_value cause)
-{
- struct ran_conn *conn = msc_conn_ref;
- int rc;
-
- rc = msc_gsm48_tx_mm_serv_rej(conn, cause);
-
- if (conn->received_cm_service_request) {
- conn->received_cm_service_request = false;
- ran_conn_put(conn, RAN_CONN_USE_CM_SERVICE);
- }
-
- return rc;
-}
-
-/* For msc_vlr_set_ciph_mode() */
-osmo_static_assert(sizeof(((struct gsm0808_encrypt_info*)0)->key) >= sizeof(((struct osmo_auth_vector*)0)->kc),
- gsm0808_encrypt_info_key_fits_osmo_auth_vec_kc);
-
-int ran_conn_geran_set_cipher_mode(struct ran_conn *conn, bool umts_aka, bool retrieve_imeisv)
-{
- struct gsm_network *net;
- struct gsm0808_encrypt_info ei;
- int i, j = 0;
- int request_classmark = 0;
- int request_classmark_for_a5_n = 0;
- struct vlr_auth_tuple *tuple;
-
- if (!conn || !conn->vsub || !conn->vsub->last_tuple) {
- /* This should really never happen, because we checked this in msc_vlr_set_ciph_mode()
- * already. */
- LOGP(DMM, LOGL_ERROR, "Internal error: missing state during Ciphering Mode Command\n");
- return -EINVAL;
- }
-
- net = conn->network;
- tuple = conn->vsub->last_tuple;
-
- for (i = 0; i < 8; i++) {
- int supported;
-
- /* A5/n permitted by osmo-msc.cfg? */
- if (!(net->a5_encryption_mask & (1 << i)))
- continue;
-
- /* A5/n supported by MS? */
- supported = classmark_supports_a5(&conn->vsub->classmark, i);
- if (supported == 1) {
- ei.perm_algo[j++] = vlr_ciph_to_gsm0808_alg_id(i);
- /* A higher A5/n is supported, so no need to request a Classmark
- * for support of a lesser A5/n. */
- request_classmark = 0;
- } else if (supported < 0) {
- request_classmark = -supported;
- request_classmark_for_a5_n = i;
- }
- }
- ei.perm_algo_len = j;
-
- if (request_classmark) {
- /* The highest A5/n as from osmo-msc.cfg might be available, but we are
- * still missing the Classmark information for that from the MS. First
- * ask for that. */
- LOGP(DMM, LOGL_DEBUG, "%s: to determine whether A5/%d is supported,"
- " first ask for a Classmark Update to obtain Classmark %d\n",
- vlr_subscr_name(conn->vsub), request_classmark_for_a5_n,
- request_classmark);
-
- return ran_conn_classmark_request_then_cipher_mode_cmd(conn, umts_aka, retrieve_imeisv);
- }
-
- if (ei.perm_algo_len == 0) {
- LOGP(DMM, LOGL_ERROR, "%s: cannot start ciphering, no intersection "
- "between MSC-configured and MS-supported A5 algorithms. MSC: %x MS: %s\n",
- vlr_subscr_name(conn->vsub), net->a5_encryption_mask,
- classmark_a5_name(&conn->vsub->classmark));
- return -ENOTSUP;
- }
-
- DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n", vlr_subscr_name(conn->vsub));
-
- tuple = conn->vsub->last_tuple;
-
- /* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth
- * tokens. tuple->vec.kc was calculated from the GSM algorithm and is not
- * necessarily a match for the UMTS AKA tokens. */
- if (umts_aka)
- osmo_auth_c3(ei.key, tuple->vec.ck, tuple->vec.ik);
- else
- memcpy(ei.key, tuple->vec.kc, sizeof(tuple->vec.kc));
- ei.key_len = sizeof(tuple->vec.kc);
-
- conn->geran_encr = (struct geran_encr){};
- if (ei.key_len <= sizeof(conn->geran_encr.key)) {
- memcpy(conn->geran_encr.key, ei.key, ei.key_len);
- conn->geran_encr.key_len = ei.key_len;
- }
- /* conn->geran_encr.alg_id remains unknown until we receive a Cipher Mode Complete from the BSC */
-
- return a_iface_tx_cipher_mode(conn, &ei, retrieve_imeisv);
-}
-
-/* VLR asks us to start using ciphering.
- * (Keep non-static to allow regression testing on this function.) */
-int msc_vlr_set_ciph_mode(void *msc_conn_ref,
- bool umts_aka,
- bool retrieve_imeisv)
-{
- struct ran_conn *conn = msc_conn_ref;
- struct vlr_subscr *vsub;
- struct vlr_auth_tuple *tuple;
-
- if (!conn || !conn->vsub) {
- LOGP(DMM, LOGL_ERROR, "Cannot send Ciphering Mode Command to"
- " NULL conn/subscriber");
- return -EINVAL;
- }
-
- vsub = conn->vsub;
- tuple = vsub->last_tuple;
-
- if (!tuple) {
- LOGP(DMM, LOGL_ERROR, "subscr %s: Cannot send Ciphering Mode"
- " Command: no auth tuple available\n",
- vlr_subscr_name(vsub));
- return -EINVAL;
- }
-
- switch (conn->via_ran) {
- case OSMO_RAT_GERAN_A:
- return ran_conn_geran_set_cipher_mode(conn, umts_aka, retrieve_imeisv);
-
- case OSMO_RAT_UTRAN_IU:
-#ifdef BUILD_IU
- DEBUGP(DMM, "-> SECURITY MODE CONTROL %s\n",
- vlr_subscr_name(conn->vsub));
- return ranap_iu_tx_sec_mode_cmd(conn->iu.ue_ctx, &tuple->vec, 0, 1);
-#else
- LOGP(DMM, LOGL_ERROR, "Cannot send Security Mode Control over OSMO_RAT_UTRAN_IU,"
- " built without Iu support\n");
- return -ENOTSUP;
-#endif
-
- default:
- break;
- }
- LOGP(DMM, LOGL_ERROR,
- "%s: cannot start ciphering, unknown RAN type %d\n",
- vlr_subscr_name(conn->vsub), conn->via_ran);
- return -ENOTSUP;
-}
-
-void ran_conn_rx_sec_mode_compl(struct ran_conn *conn)
+static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type,
+ enum gsm48_reject_value cause)
{
- struct vlr_ciph_result vlr_res = {};
-
- if (!conn || !conn->vsub) {
- LOGP(DMM, LOGL_ERROR,
- "Rx Security Mode Complete for invalid conn\n");
- return;
- }
-
- DEBUGP(DMM, "<- SECURITY MODE COMPLETE %s\n",
- vlr_subscr_name(conn->vsub));
-
- vlr_res.cause = VLR_CIPH_COMPL;
- vlr_subscr_rx_ciph_res(conn->vsub, &vlr_res);
+ struct msc_a *msc_a = msc_conn_ref;
+ msc_gsm48_tx_mm_serv_rej(msc_a, cause);
+ msc_a_put(msc_a, msc_a_cm_service_type_to_use(cm_service_type));
+ return 0;
}
/* VLR informs us that the subscriber data has somehow been modified */
static void msc_vlr_subscr_update(struct vlr_subscr *subscr)
{
- LOGVSUBP(LOGL_NOTICE, subscr, "VLR: update for IMSI=%s (MSISDN=%s)\n",
- subscr->imsi, subscr->msisdn);
- ran_conn_update_id_for_vsub(subscr);
-}
-
-static void update_classmark(const struct gsm_classmark *src, struct gsm_classmark *dst)
-{
- if (src->classmark1_set) {
- dst->classmark1 = src->classmark1;
- dst->classmark1_set = true;
- }
- if (src->classmark2_len) {
- dst->classmark2_len = src->classmark2_len;
- memcpy(dst->classmark2, src->classmark2, sizeof(dst->classmark2));
- }
- if (src->classmark3_len) {
- dst->classmark3_len = src->classmark3_len;
- memcpy(dst->classmark3, src->classmark3, sizeof(dst->classmark3));
- }
+ struct msub *msub = msub_for_vsub(subscr);
+ LOGVSUBP(LOGL_NOTICE, subscr, "VLR: update for IMSI=%s (MSISDN=%s)%s\n",
+ subscr->imsi, subscr->msisdn, msub ? "" : " (NO CONN!)");
+ msub_update_id(msub);
}
/* VLR informs us that the subscriber has been associated with a conn */
static int msc_vlr_subscr_assoc(void *msc_conn_ref,
struct vlr_subscr *vsub)
{
- struct ran_conn *conn = msc_conn_ref;
+ struct msc_a *msc_a = msc_conn_ref;
+ struct msub *msub = msc_a->c.msub;
OSMO_ASSERT(vsub);
- if (conn->vsub) {
- if (conn->vsub == vsub)
- LOG_RAN_CONN(conn, LOGL_NOTICE, "msc_vlr_subscr_assoc(): conn already associated with %s\n",
- vlr_subscr_name(vsub));
- else {
- LOG_RAN_CONN(conn, LOGL_ERROR, "msc_vlr_subscr_assoc(): conn already associated with a subscriber,"
- " cannot associate with %s\n", vlr_subscr_name(vsub));
- return -EINVAL;
- }
- }
- vlr_subscr_get(vsub, VSUB_USE_CONN);
- conn->vsub = vsub;
- OSMO_ASSERT(conn->vsub);
- conn->vsub->cs.attached_via_ran = conn->via_ran;
+ if (msub_set_vsub(msub, vsub))
+ return -EINVAL;
+ vsub->cs.attached_via_ran = msc_a->c.ran->type;
/* In case we have already received Classmark Information before the VLR Subscriber was
* associated with the conn: merge the new Classmark into vsub->classmark. Don't overwrite valid
* vsub->classmark with unset classmark, though. */
- update_classmark(&conn->temporary_classmark, &conn->vsub->classmark);
- ran_conn_update_id(conn);
- return 0;
-}
+ osmo_gsm48_classmark_update(&vsub->classmark, &msc_a->temporary_classmark);
-static int msc_vlr_route_gsup_msg(struct vlr_subscr *vsub,
- struct osmo_gsup_message *gsup_msg)
-{
- switch (gsup_msg->message_type) {
- /* GSM 09.11 code implementing SS/USSD */
- case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
- case OSMO_GSUP_MSGT_PROC_SS_RESULT:
- case OSMO_GSUP_MSGT_PROC_SS_ERROR:
- DEBUGP(DMSC, "Routed to GSM 09.11 SS/USSD handler\n");
- return gsm0911_gsup_handler(vsub, gsup_msg);
-
- /* GSM 04.11 code implementing MO SMS */
- case OSMO_GSUP_MSGT_MO_FORWARD_SM_ERROR:
- case OSMO_GSUP_MSGT_MO_FORWARD_SM_RESULT:
- case OSMO_GSUP_MSGT_READY_FOR_SM_ERROR:
- case OSMO_GSUP_MSGT_READY_FOR_SM_RESULT:
- DEBUGP(DMSC, "Routed to GSM 04.11 MO handler\n");
- return gsm411_gsup_mo_handler(vsub, gsup_msg);
-
- /* GSM 04.11 code implementing MT SMS */
- case OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST:
- DEBUGP(DMSC, "Routed to GSM 04.11 MT handler\n");
- return gsm411_gsup_mt_handler(vsub, gsup_msg);
+ msub_update_id(msub);
- default:
- LOGP(DMM, LOGL_ERROR, "No handler found for %s, dropping message...\n",
- osmo_gsup_message_type_name(gsup_msg->message_type));
- return -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
- }
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_COMPLETE_LAYER_3_OK, NULL);
+ return 0;
}
/* operations that we need to implement for libvlr */
-static const struct vlr_ops msc_vlr_ops = {
+const struct vlr_ops msc_vlr_ops = {
.tx_auth_req = msc_vlr_tx_auth_req,
.tx_auth_rej = msc_vlr_tx_auth_rej,
.tx_id_req = msc_vlr_tx_id_req,
@@ -1859,39 +1377,13 @@ static const struct vlr_ops msc_vlr_ops = {
.tx_lu_rej = msc_vlr_tx_lu_rej,
.tx_cm_serv_acc = msc_vlr_tx_cm_serv_acc,
.tx_cm_serv_rej = msc_vlr_tx_cm_serv_rej,
- .set_ciph_mode = msc_vlr_set_ciph_mode,
+ .set_ciph_mode = msc_a_vlr_set_cipher_mode,
.tx_common_id = msc_vlr_tx_common_id,
.tx_mm_info = msc_vlr_tx_mm_info,
.subscr_update = msc_vlr_subscr_update,
.subscr_assoc = msc_vlr_subscr_assoc,
- .forward_gsup_msg = msc_vlr_route_gsup_msg,
};
-/* Allocate net->vlr so that the VTY may configure the VLR's data structures */
-int msc_vlr_alloc(struct gsm_network *net)
-{
- net->vlr = vlr_alloc(net, &msc_vlr_ops);
- if (!net->vlr)
- return -ENOMEM;
- net->vlr->user_ctx = net;
- return 0;
-}
-
-/* Launch the VLR, i.e. its GSUP connection */
-int msc_vlr_start(struct gsm_network *net)
-{
- struct ipaccess_unit *ipa_dev;
-
- OSMO_ASSERT(net->vlr);
-
- ipa_dev = talloc_zero(net->vlr, struct ipaccess_unit);
- ipa_dev->unit_name = "MSC";
- ipa_dev->serno = net->msc_ipa_name; /* NULL unless configured via VTY */
- ipa_dev->swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
-
- return vlr_start(ipa_dev, net->vlr, net->gsup_server_addr_str, net->gsup_server_port);
-}
-
struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value)
{
struct msgb *msg;
diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c
index 62b5d12eb..aa9764968 100644
--- a/src/libmsc/gsm_04_08_cc.c
+++ b/src/libmsc/gsm_04_08_cc.c
@@ -48,7 +48,13 @@
#include <osmocom/abis/e1_input.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/paging.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/mncc_call.h>
+#include <osmocom/msc/msc_t.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm0480.h>
@@ -60,17 +66,29 @@
#include <osmocom/core/byteswap.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/crypt/auth.h>
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#endif
-
-#include <osmocom/msc/msc_ifaces.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/msc_mgcp.h>
#include <assert.h>
-static uint32_t new_callref = 0x80000001;
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+
+static int trans_tx_gsm48(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
+ gh->proto_discr = GSM48_PDISC_CC | (trans->transaction_id << 4);
+ OMSC_LINKID_CB(msg) = trans->dlci;
+
+ return msc_a_tx_dtap_to_i(trans->msc_a, msg);
+}
+
+uint32_t msc_cc_next_outgoing_callref() {
+ static uint32_t last_callref = 0x80000000;
+ last_callref++;
+ if (last_callref < 0x80000001)
+ last_callref = 0x80000001;
+ return last_callref;
+}
static void gsm48_cc_guard_timeout(void *arg)
{
@@ -127,7 +145,7 @@ int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message)
data[0] = ss_notify->len - 1;
gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh));
gh->msg_type = GSM48_MT_CC_FACILITY;
- return gsm48_conn_sendmsg(ss_notify, trans->conn, trans);
+ return trans_tx_gsm48(trans, ss_notify);
}
/* FIXME: this count_statistics is a state machine behaviour. we should convert
@@ -163,11 +181,6 @@ static void count_statistics(struct gsm_trans *trans, int new_state)
}
}
-/* The entire call control code is written in accordance with Figure 7.10c
- * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE
- * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY
- * it for voice */
-
static void new_cc_state(struct gsm_trans *trans, int state)
{
if (state > 31 || state < 0)
@@ -201,7 +214,7 @@ static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg)
call_state = msgb_put(msg, 1);
call_state[0] = 0xc0 | 0x00;
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static void gsm48_stop_cc_timer(struct gsm_trans *trans)
@@ -254,11 +267,16 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans)
{
gsm48_stop_cc_timer(trans);
- /* Initiate the teardown of the related connections on the MGW */
- msc_mgcp_call_release(trans);
-
/* send release to L4, if callref still exists */
if (trans->callref) {
+ /* FIXME: currently, a CC trans that would not yet be in state GSM_CSTATE_RELEASE_REQ fails to send a
+ * CC Release to the MS if it gets freed here. Hack it to do so. */
+ if (trans->cc.state != GSM_CSTATE_RELEASE_REQ) {
+ struct gsm_mncc rel = {};
+ rel.callref = trans->callref;
+ mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ gsm48_cc_tx_release(trans, &rel);
+ }
/* Ressource unavailable */
mncc_release_ind(trans->net, trans, trans->callref,
GSM48_CAUSE_LOC_PRN_S_LU,
@@ -271,60 +289,57 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans)
new_cc_state(trans, GSM_CSTATE_NULL);
gsm48_stop_guard_timer(trans);
-}
-static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+ if (trans->msc_a && trans->msc_a->cc.active_trans == trans)
+ trans->msc_a->cc.active_trans = NULL;
+}
/* call-back from paging the B-end of the connection */
-static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *_conn, void *_transt)
+static void cc_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans)
{
- struct ran_conn *conn = _conn;
- struct gsm_trans *transt = _transt;
- enum gsm_paging_event paging_event = event;
-
- OSMO_ASSERT(!transt->conn);
+ if (trans->msc_a) {
+ LOG_MSC_A_CAT(msc_a, DPAG, LOGL_ERROR,
+ "Handle paging error: transaction already associated with subscriber,"
+ " apparently it was already handled. Skip.\n");
+ return;
+ }
- switch (paging_event) {
- case GSM_PAGING_SUCCEEDED:
- LOG_TRANS(transt, LOGL_DEBUG, "Paging succeeded\n");
- OSMO_ASSERT(conn);
+ if (msc_a) {
+ LOG_TRANS(trans, LOGL_DEBUG, "Paging succeeded\n");
/* Assign conn */
- transt->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_CC);
- transt->paging_request = NULL;
+ msc_a_get(msc_a, MSC_A_USE_CC);
+ trans->msc_a = msc_a;
+ trans->paging_request = NULL;
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans);
/* send SETUP request to called party */
- gsm48_cc_tx_setup(transt, &transt->cc.msg);
- break;
- case GSM_PAGING_EXPIRED:
- case GSM_PAGING_BUSY:
- LOG_TRANS(transt, LOGL_DEBUG, "Paging expired\n");
+ gsm48_cc_tx_setup(trans, &trans->cc.msg);
+ } else {
+ LOG_TRANS(trans, LOGL_DEBUG, "Paging expired\n");
/* Temporarily out of order */
- mncc_release_ind(transt->net, transt,
- transt->callref,
+ mncc_release_ind(trans->net, trans,
+ trans->callref,
GSM48_CAUSE_LOC_PRN_S_LU,
GSM48_CC_CAUSE_DEST_OOO);
- transt->callref = 0;
- transt->paging_request = NULL;
- trans_free(transt);
- break;
+ trans->callref = 0;
+ trans->paging_request = NULL;
+ trans_free(trans);
}
-
- return 0;
}
/* bridge channels of two transactions */
-static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge)
+static int tch_bridge(struct gsm_network *net, const struct gsm_mncc_bridge *bridge)
{
struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[0]);
struct gsm_trans *trans2 = trans_find_by_callref(net, bridge->callref[1]);
- int rc;
+ struct call_leg *cl1;
+ struct call_leg *cl2;
if (!trans1 || !trans2) {
LOG_TRANS(trans1 ? : trans2, LOGL_ERROR, "Cannot MNCC_BRIDGE, one or both call legs are unset\n");
return -EIO;
}
- if (!trans1->conn || !trans2->conn) {
+ if (!trans1->msc_a || !trans2->msc_a) {
LOG_TRANS(trans1, LOGL_ERROR, "Cannot MNCC_BRIDGE, one or both call legs lack an active connection\n");
LOG_TRANS(trans2, LOGL_ERROR, "Cannot MNCC_BRIDGE, one or both call legs lack an active connection\n");
return -EIO;
@@ -333,30 +348,14 @@ static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge)
LOG_TRANS(trans1, LOGL_DEBUG, "MNCC_BRIDGE: Local bridge to callref 0x%x\n", trans2->callref);
LOG_TRANS(trans2, LOGL_DEBUG, "MNCC_BRIDGE: Local bridge to callref 0x%x\n", trans1->callref);
- /* Which subscriber do we want to track trans1 or trans2? */
- log_set_context(LOG_CTX_VLR_SUBSCR, trans1->vsub);
-
- /* This call briding mechanism is only used with the internal MNCC.
- * functionality (with external MNCC briding would be done by the PBX)
- * This means we may just copy the codec info we have for the RAN side
- * of the first leg to the CN side of both legs. This also means that
- * if both legs use different codecs the MGW must perform transcoding
- * on the second leg. */
- trans1->conn->rtp.codec_cn = trans1->conn->rtp.codec_ran;
- trans2->conn->rtp.codec_cn = trans1->conn->rtp.codec_ran;
-
- /* Bridge RTP streams */
- rc = msc_mgcp_call_complete(trans1, trans2->conn->rtp.local_port_cn,
- trans2->conn->rtp.local_addr_cn);
- if (rc)
- return -EINVAL;
-
- rc = msc_mgcp_call_complete(trans2, trans1->conn->rtp.local_port_cn,
- trans1->conn->rtp.local_addr_cn);
- if (rc)
- return -EINVAL;
+ /* This call bridging mechanism is only used with the internal MNCC (with external MNCC briding would be done by
+ * the PBX). For inter-MSC Handover scenarios, an external MNCC is mandatory. The conclusion is that in this
+ * code path, there is only one MSC, and the MSC-I role is local, and hence we can directly access the ran_conn.
+ * If we can't, then we must give up. */
+ cl1 = trans1->msc_a->cc.call_leg;
+ cl2 = trans2->msc_a->cc.call_leg;
- return 0;
+ return call_leg_local_bridge(cl1, trans1->callref, trans1, cl2, trans2->callref, trans2);
}
static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
@@ -365,9 +364,6 @@ static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
return gsm48_cc_tx_status(trans, msg);
}
-static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
-static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
-
static void gsm48_cc_timeout(void *arg)
{
struct gsm_trans *trans = arg;
@@ -452,7 +448,7 @@ static void gsm48_cc_timeout(void *arg)
/* disconnect both calls from the bridge */
static inline void disconnect_bridge(struct gsm_network *net,
- struct gsm_mncc_bridge *bridge, int err)
+ const struct gsm_mncc_bridge *bridge, int err)
{
struct gsm_trans *trans0 = trans_find_by_callref(net, bridge->callref[0]);
struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[1]);
@@ -527,7 +523,7 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
/* 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,
+ memcpy(&trans->bearer_cap, &setup.bearer_cap,
sizeof(trans->bearer_cap));
}
/* facility */
@@ -606,7 +602,7 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
}
/* Get free transaction_id */
- trans_id = trans_assign_trans_id(trans->net, trans->vsub, GSM48_PDISC_CC);
+ trans_id = trans_assign_trans_id(trans->net, trans->vsub, TRANS_CC);
if (trans_id < 0) {
/* no free transaction ID */
rc = mncc_release_ind(trans->net, trans, trans->callref,
@@ -655,7 +651,7 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP]);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
@@ -711,7 +707,7 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
/* Assign call (if not done yet) */
- rc = msc_mgcp_try_call_assignment(trans);
+ rc = msc_a_try_call_assignment(trans);
/* don't continue, if there were problems with
* the call assignment. */
@@ -745,12 +741,12 @@ static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg)
if (proceeding->fields & MNCC_F_PROGRESS)
gsm48_encode_progress(msg, 0, &proceeding->progress);
- rc = gsm48_conn_sendmsg(msg, trans->conn, trans);
+ rc = trans_tx_gsm48(trans, msg);
if (rc)
return rc;
/* Assign call (if not done yet) */
- return msc_mgcp_try_call_assignment(trans);
+ return msc_a_try_call_assignment(trans);
}
static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
@@ -812,7 +808,7 @@ static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
@@ -829,7 +825,7 @@ static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
if (progress->fields & MNCC_F_USERUSER)
gsm48_encode_useruser(msg, 0, &progress->useruser);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
@@ -858,7 +854,7 @@ static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_CONNECT_IND);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
@@ -929,7 +925,7 @@ static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_ACTIVE);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
@@ -972,7 +968,6 @@ static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
}
return mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &disc);
-
}
static struct gsm_mncc_cause default_cause = {
@@ -1017,7 +1012,7 @@ static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
@@ -1062,7 +1057,7 @@ static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
/* release collision 5.4.5 */
rc = mncc_recvmsg(trans->net, trans, MNCC_REL_CNF, &rel);
} else {
- rc = gsm48_tx_simple(trans->conn,
+ rc = gsm48_tx_simple(trans->msc_a,
GSM48_PDISC_CC | (trans->transaction_id << 4),
GSM48_MT_CC_RELEASE_COMPL);
rc = mncc_recvmsg(trans->net, trans, MNCC_REL_IND, &rel);
@@ -1103,7 +1098,7 @@ static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
@@ -1189,7 +1184,7 @@ static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
if (rel->fields & MNCC_F_USERUSER)
gsm48_encode_useruser(msg, 0, &rel->useruser);
- ret = gsm48_conn_sendmsg(msg, trans->conn, trans);
+ ret = trans_tx_gsm48(trans, msg);
trans_free(trans);
@@ -1233,7 +1228,7 @@ static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
/* facility */
gsm48_encode_facility(msg, 1, &fac->facility);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg)
@@ -1252,7 +1247,7 @@ static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg)
gh->msg_type = GSM48_MT_CC_HOLD_ACK;
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
@@ -1269,7 +1264,7 @@ static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
else
gsm48_encode_cause(msg, 1, &default_cause);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg)
@@ -1289,7 +1284,7 @@ static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg)
gh->msg_type = GSM48_MT_CC_RETR_ACK;
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
@@ -1306,7 +1301,7 @@ static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
else
gsm48_encode_cause(msg, 1, &default_cause);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg)
@@ -1341,7 +1336,7 @@ static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg)
if (dtmf->fields & MNCC_F_KEYPAD)
gsm48_encode_keypad(msg, dtmf->keypad);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
@@ -1358,7 +1353,7 @@ static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
else
gsm48_encode_cause(msg, 1, &default_cause);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
@@ -1368,7 +1363,7 @@ static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK;
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg)
@@ -1425,7 +1420,7 @@ static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg)
@@ -1472,7 +1467,7 @@ static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_ACTIVE);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
@@ -1527,7 +1522,7 @@ static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
new_cc_state(trans, GSM_CSTATE_ACTIVE);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
@@ -1541,7 +1536,7 @@ static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
/* notify */
gsm48_encode_notify(msg, notify->notify);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
@@ -1575,7 +1570,7 @@ static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
if (user->more)
gsm48_encode_more(msg);
- return gsm48_conn_sendmsg(msg, trans->conn, trans);
+ return trans_tx_gsm48(trans, msg);
}
static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
@@ -1601,9 +1596,9 @@ 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, struct gsm_trans *trans, uint32_t callref,
- int cmd, uint32_t addr, uint16_t port, uint32_t payload_type,
- uint32_t payload_msg_type)
+static int mncc_recv_rtp(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref,
+ int cmd, struct osmo_sockaddr_str *rtp_addr, uint32_t payload_type,
+ uint32_t payload_msg_type)
{
uint8_t data[sizeof(struct gsm_mncc)];
struct gsm_mncc_rtp *rtp;
@@ -1613,55 +1608,18 @@ static void mncc_recv_rtp(struct gsm_network *net, struct gsm_trans *trans, uint
rtp->callref = callref;
rtp->msg_type = cmd;
- rtp->ip = osmo_htonl(addr);
- rtp->port = port;
+ if (rtp_addr) {
+ rtp->ip = osmo_htonl(inet_addr(rtp_addr->ip));
+ rtp->port = rtp_addr->port;
+ }
rtp->payload_type = payload_type;
rtp->payload_msg_type = payload_msg_type;
- mncc_recvmsg(net, trans, 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 = inet_addr(trans->conn->rtp.local_addr_cn);
- uint16_t port = trans->conn->rtp.local_port_cn;
-
- if (addr == INADDR_NONE) {
- LOGP(DMNCC, LOGL_ERROR,
- "(subscriber:%s) external MNCC is signalling invalid IP-Address\n",
- vlr_subscr_name(trans->vsub));
- return;
- }
- if (port == 0) {
- LOGP(DMNCC, LOGL_ERROR,
- "(subscriber:%s) external MNCC is signalling invalid Port\n",
- vlr_subscr_name(trans->vsub));
- return;
- }
-
- /* 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, trans->callref, cmd,
- addr,
- port,
- payload_type,
- msg_type);
+ return mncc_recvmsg(net, trans, cmd, (struct gsm_mncc *)data);
}
static void mncc_recv_rtp_err(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref, int cmd)
{
- return mncc_recv_rtp(net, trans, callref, cmd, 0, 0, 0, 0);
+ mncc_recv_rtp(net, trans, callref, cmd, NULL, 0, 0);
}
static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
@@ -1676,7 +1634,7 @@ static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
return -EIO;
}
log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
- if (!trans->conn) {
+ if (!trans->msc_a) {
LOG_TRANS_CAT(trans, DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
mncc_recv_rtp_err(net, trans, callref, MNCC_RTP_CREATE);
return 0;
@@ -1698,7 +1656,7 @@ static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
trans->tch_rtp_create = true;
/* Assign call (if not done yet) */
- return msc_mgcp_try_call_assignment(trans);
+ return msc_a_try_call_assignment(trans);
}
/* Trigger TCH_RTP_CREATE acknowledgement */
@@ -1707,18 +1665,38 @@ 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 msc_a *msc_a = trans->msc_a;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct osmo_sockaddr_str *rtp_cn_local;
+ /* 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;
+ 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;
- struct ran_conn *conn = trans->conn;
- struct gsm_network *network = conn->network;
+ rtp_cn_local = call_leg_local_ip(cl, RTP_TO_CN);
+ if (!rtp_cn_local) {
+ LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "Cannot RTP CREATE to MNCC, no local RTP IP:port set up\n");
+ return -EINVAL;
+ }
- mncc_recv_rtp_sock(network, trans, MNCC_RTP_CREATE);
- return 0;
+ return mncc_recv_rtp(net, trans, trans->callref, MNCC_RTP_CREATE, rtp_cn_local, payload_type, msg_type);
}
-static int tch_rtp_connect(struct gsm_network *net, struct gsm_mncc_rtp *rtp)
+static int tch_rtp_connect(struct gsm_network *net, const struct gsm_mncc_rtp *rtp)
{
struct gsm_trans *trans;
- struct in_addr addr;
+ struct call_leg *cl;
+ struct rtp_stream *rtps;
+ struct osmo_sockaddr_str rtp_addr;
/* FIXME: in *rtp we should get the codec information of the remote
* leg. We will have to populate trans->conn->rtp.codec_cn with a
@@ -1738,16 +1716,29 @@ static int tch_rtp_connect(struct gsm_network *net, struct gsm_mncc_rtp *rtp)
return -EIO;
}
log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
- if (!trans->conn) {
+ if (!trans->msc_a) {
LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n");
mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CONNECT);
- return 0;
+ return -EIO;
+ }
+
+ LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(MNCC_RTP_CONNECT));
+
+ cl = trans->msc_a->cc.call_leg;
+ rtps = cl ? cl->rtp[RTP_TO_CN] : NULL;
+
+ if (!rtps) {
+ LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP connect for trans without ongoing call\n");
+ mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CONNECT);
+ return -EINVAL;
}
LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(MNCC_RTP_CONNECT));
- addr.s_addr = osmo_htonl(rtp->ip);
- return msc_mgcp_call_complete(trans, rtp->port, inet_ntoa(addr));
+ osmo_sockaddr_str_from_32n(&rtp_addr, rtp->ip, rtp->port);
+ rtp_stream_set_remote_addr(rtps, &rtp_addr);
+ rtp_stream_commit(rtps);
+ return 0;
}
static struct downstate {
@@ -1809,24 +1800,24 @@ static struct downstate {
(sizeof(downstatelist) / sizeof(struct downstate))
-int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
+int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg)
{
int i, rc = 0;
- struct gsm_trans *trans = NULL, *transt;
- struct ran_conn *conn = NULL;
- struct gsm_mncc *data = arg, rel;
+ struct msc_a *msc_a = NULL;
+ struct gsm_trans *trans = NULL;
+ const struct gsm_mncc *data;
/* handle special messages */
- switch(msg_type) {
+ switch(msg->msg_type) {
case MNCC_BRIDGE:
- rc = tch_bridge(net, arg);
+ rc = tch_bridge(net, &msg->bridge);
if (rc < 0)
- disconnect_bridge(net, arg, -rc);
+ disconnect_bridge(net, &msg->bridge, -rc);
return rc;
case MNCC_RTP_CREATE:
- return tch_rtp_create(net, data->callref);
+ return tch_rtp_create(net, msg->rtp.callref);
case MNCC_RTP_CONNECT:
- return tch_rtp_connect(net, arg);
+ return tch_rtp_connect(net, &msg->rtp);
case MNCC_RTP_FREE:
/* unused right now */
return -EIO;
@@ -1838,12 +1829,11 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
case GSM_TCHH_FRAME:
case GSM_TCH_FRAME_AMR:
LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP streams must be handled externally; %s not supported.\n",
- get_mncc_name(msg_type));
+ get_mncc_name(msg->msg_type));
return -ENOTSUP;
}
- memset(&rel, 0, sizeof(struct gsm_mncc));
- rel.callref = data->callref;
+ data = &msg->signal;
/* Find callref */
trans = trans_find_by_callref(net, data->callref);
@@ -1852,9 +1842,9 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
if (!trans) {
struct vlr_subscr *vsub;
- if (msg_type != MNCC_SETUP_REQ) {
+ if (msg->msg_type != MNCC_SETUP_REQ) {
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "Unknown call reference for %s\n",
- get_mncc_name(msg_type));
+ get_mncc_name(msg->msg_type));
/* Invalid call reference */
return mncc_release_ind(net, NULL, data->callref,
GSM48_CAUSE_LOC_PRN_S_LU,
@@ -1862,7 +1852,7 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
}
if (!data->called.number[0] && !data->imsi[0]) {
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "Neither number nor IMSI in %s\n",
- get_mncc_name(msg_type));
+ get_mncc_name(msg->msg_type));
/* Invalid number */
return mncc_release_ind(net, NULL, data->callref,
GSM48_CAUSE_LOC_PRN_S_LU,
@@ -1873,12 +1863,12 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
vsub = vlr_subscr_find_by_msisdn(net->vlr, data->called.number, __func__);
if (!vsub)
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "rx %s for unknown subscriber number '%s'\n",
- get_mncc_name(msg_type), data->called.number);
+ get_mncc_name(msg->msg_type), data->called.number);
} else {
vsub = vlr_subscr_find_by_imsi(net->vlr, data->imsi, __func__);
if (!vsub)
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "rx %s for unknown subscriber IMSI '%s'\n",
- get_mncc_name(msg_type), data->imsi);
+ get_mncc_name(msg->msg_type), data->imsi);
}
if (!vsub)
return mncc_release_ind(net, NULL, data->callref, GSM48_CAUSE_LOC_PRN_S_LU,
@@ -1889,7 +1879,7 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
/* If subscriber is not "attached" */
if (!vsub->lu_complete) {
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "rx %s for subscriber that is not attached: %s\n",
- get_mncc_name(msg_type), vlr_subscr_name(vsub));
+ get_mncc_name(msg->msg_type), vlr_subscr_name(vsub));
vlr_subscr_put(vsub, __func__);
/* Temporarily out of order */
return mncc_release_ind(net, NULL, data->callref,
@@ -1897,7 +1887,7 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
GSM48_CC_CAUSE_DEST_OOO);
}
/* Create transaction */
- trans = trans_alloc(net, vsub, GSM48_PDISC_CC,
+ trans = trans_alloc(net, vsub, TRANS_CC,
TRANS_ID_UNASSIGNED, data->callref);
if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, "No memory for trans.\n");
@@ -1909,20 +1899,16 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
return -ENOMEM;
}
- /* Find conn */
- conn = connection_for_subscr(vsub);
+ /* Find valid conn */
+ msc_a = msc_a_for_vsub(vsub, true);
/* If subscriber has no conn */
- if (!conn) {
- /* find transaction with this subscriber already paging */
- llist_for_each_entry(transt, &net->trans_list, entry) {
- /* Transaction of our conn? */
- if (transt == trans ||
- transt->vsub != vsub)
- continue;
+ if (!msc_a) {
+
+ if (vsub->cs.is_paging) {
LOG_TRANS(trans, LOGL_DEBUG,
"rx %s, subscriber not yet connected, paging already started\n",
- get_mncc_name(msg_type));
+ get_mncc_name(msg->msg_type));
vlr_subscr_put(vsub, __func__);
trans_free(trans);
return 0;
@@ -1932,24 +1918,19 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
/* Request a channel */
- trans->paging_request = subscr_request_conn(
- vsub,
- setup_trig_pag_evt,
- trans,
- "MNCC: establish call",
- SGSAP_SERV_IND_CS_CALL);
+ trans->paging_request = paging_request_start(vsub, PAGING_CAUSE_CALL_CONVERSATIONAL,
+ cc_paging_cb, trans, "MNCC: establish call");
if (!trans->paging_request) {
LOG_TRANS(trans, LOGL_ERROR, "Failed to allocate paging token.\n");
- vlr_subscr_put(vsub, __func__);
trans_free(trans);
- return 0;
}
vlr_subscr_put(vsub, __func__);
return 0;
}
/* Assign conn */
- trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_CC);
+ trans->msc_a = msc_a;
+ msc_a_get(msc_a, MSC_A_USE_CC);
trans->dlci = 0x00; /* SAPI=0, not SACCH */
vlr_subscr_put(vsub, __func__);
} else {
@@ -1957,19 +1938,22 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
}
- LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(msg_type));
+ LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(msg->msg_type));
gsm48_start_guard_timer(trans);
- if (trans->conn)
- conn = trans->conn;
+ if (trans->msc_a)
+ msc_a = trans->msc_a;
/* if paging did not respond yet */
- if (!conn) {
- LOG_TRANS(trans, LOGL_DEBUG, "rx %s in paging state\n", get_mncc_name(msg_type));
+ if (!msc_a) {
+ struct gsm_mncc rel = {
+ .callref = data->callref,
+ };
+ LOG_TRANS(trans, LOGL_DEBUG, "rx %s in paging state\n", get_mncc_name(msg->msg_type));
mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
GSM48_CC_CAUSE_NORM_CALL_CLEAR);
- if (msg_type == MNCC_REL_REQ)
+ if (msg->msg_type == MNCC_REL_REQ)
rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
else
rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
@@ -1978,25 +1962,83 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
return rc;
} else {
LOG_TRANS(trans, LOGL_DEBUG, "rx %s in state %s\n",
- get_mncc_name(msg_type), gsm48_cc_state_name(trans->cc.state));
+ get_mncc_name(msg->msg_type), gsm48_cc_state_name(trans->cc.state));
}
/* Find function for current state and message */
for (i = 0; i < DOWNSLLEN; i++)
- if ((msg_type == downstatelist[i].type)
+ if ((msg->msg_type == downstatelist[i].type)
&& ((1 << trans->cc.state) & downstatelist[i].states))
break;
if (i == DOWNSLLEN) {
LOG_TRANS(trans, LOGL_DEBUG, "Message '%s' unhandled at state '%s'\n",
- get_mncc_name(msg_type), gsm48_cc_state_name(trans->cc.state));
+ get_mncc_name(msg->msg_type), gsm48_cc_state_name(trans->cc.state));
return 0;
}
- rc = downstatelist[i].rout(trans, arg);
+ rc = downstatelist[i].rout(trans, (void*)msg);
return rc;
}
+struct mncc_call *mncc_find_by_callref_from_msg(const union mncc_msg *msg)
+{
+ uint32_t callref;
+
+ switch (msg->msg_type) {
+ case MNCC_BRIDGE:
+ callref = msg->bridge.callref[0];
+ break;
+ case MNCC_RTP_CREATE:
+ case MNCC_RTP_CONNECT:
+ callref = msg->rtp.callref;
+ break;
+
+ case MNCC_RTP_FREE:
+ case MNCC_FRAME_DROP:
+ case MNCC_FRAME_RECV:
+ case GSM_TCHF_FRAME:
+ case GSM_TCHF_FRAME_EFR:
+ case GSM_TCHH_FRAME:
+ case GSM_TCH_FRAME_AMR:
+ return NULL;
+
+ default:
+ callref = msg->signal.callref;
+ break;
+ }
+
+ return mncc_call_find_by_callref(callref);
+}
+
+/* Demux incoming genuine calls to GSM CC from MNCC forwarding for inter-MSC handover */
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
+{
+ const union mncc_msg *msg = arg;
+ struct mncc_call *mncc_call = NULL;
+ OSMO_ASSERT(msg_type == msg->msg_type);
+
+ if (msg->msg_type == MNCC_SETUP_REQ) {
+ /* Incoming call to forward for inter-MSC Handover? */
+ mncc_call = msc_t_check_call_to_handover_number(&msg->signal);
+ if (mncc_call)
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG,
+ "Incoming call matches pending inter-MSC Handover Number\n");
+ }
+ if (!mncc_call) {
+ /* Find already active MNCC FSM for this callref.
+ * Currently only for inter-MSC call forwarding, but mncc_fsm could at some point also be used for direct
+ * MNCC<->GSM-CC call handling. */
+ mncc_call = mncc_find_by_callref_from_msg(msg);
+ }
+ if (mncc_call) {
+ mncc_call_rx(mncc_call, msg);
+ return 0;
+ }
+
+ /* None of the above? Then it must be a normal GSM CC call related message. */
+ return mncc_tx_to_gsm_cc(net, msg);
+}
static struct datastate {
uint32_t states;
@@ -2052,12 +2094,14 @@ static struct datastate {
#define DATASLLEN \
(sizeof(datastatelist) / sizeof(struct datastate))
-int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg)
+int gsm0408_rcv_cc(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t msg_type = gsm48_hdr_msg_type(gh);
uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
struct gsm_trans *trans = NULL;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm_network *net = msc_a_net(msc_a);
int i, rc = 0;
if (msg_type & 0x80) {
@@ -2065,33 +2109,44 @@ int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg)
return -EINVAL;
}
- if (!conn->vsub) {
+ if (!vsub) {
LOG_TRANS(trans, LOGL_ERROR, "Invalid conn: no subscriber\n");
return -EINVAL;
}
/* Find transaction */
- trans = trans_find_by_id(conn, GSM48_PDISC_CC, transaction_id);
+ trans = trans_find_by_id(msc_a, TRANS_CC, transaction_id);
/* Create transaction */
if (!trans) {
- DEBUGP(DCC, "Unknown transaction ID %x, "
- "creating new trans.\n", transaction_id);
/* Create transaction */
- trans = trans_alloc(conn->network, conn->vsub,
- GSM48_PDISC_CC,
- transaction_id, new_callref++);
+ trans = trans_alloc(net, vsub,
+ TRANS_CC,
+ transaction_id, msc_cc_next_outgoing_callref());
if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, "No memory for trans.\n");
- rc = gsm48_tx_simple(conn,
+ rc = gsm48_tx_simple(msc_a,
GSM48_PDISC_CC | (transaction_id << 4),
GSM48_MT_CC_RELEASE_COMPL);
return -ENOMEM;
}
+ if (osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Not allowed to accept CC transaction\n");
+ trans_free(trans);
+ return -EINVAL;
+ }
+
/* Assign transaction */
- trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_CC);
+ msc_a_get(msc_a, MSC_A_USE_CC);
+ trans->msc_a = msc_a;
trans->dlci = OMSC_LINKID_CB(msg); /* DLCI as received from BSC */
- cm_service_request_concludes(conn, msg);
+
+ /* An earlier CM Service Request for this CC message now has concluded */
+ if (!osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_CC))
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Creating new CC transaction without prior CM Service Request\n");
+ else
+ msc_a_put(msc_a, MSC_A_USE_CM_SERVICE_CC);
}
LOG_TRANS(trans, LOGL_DEBUG, "rx %s in state %s\n", gsm48_cc_msg_name(msg_type),
@@ -2104,6 +2159,14 @@ int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg)
break;
if (i == DATASLLEN) {
LOG_TRANS(trans, LOGL_ERROR, "Message unhandled at this state.\n");
+
+ /* If a transaction was just now created, it was a bogus transaction ID, and we need to clean up the
+ * transaction right away. */
+ if (trans->cc.state == GSM_CSTATE_NULL) {
+ LOG_TRANS(trans, LOGL_ERROR, "Unknown transaction ID for non-SETUP message is not allowed"
+ " -- disarding new CC transaction right away\n");
+ trans_free(trans);
+ }
return 0;
}
@@ -2111,6 +2174,5 @@ int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg)
rc = datastatelist[i].rout(trans, msg);
- ran_conn_communicating(conn);
return rc;
}
diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c
index 85b861cfb..60cdaee6e 100644
--- a/src/libmsc/gsm_04_11.c
+++ b/src/libmsc/gsm_04_11.c
@@ -51,8 +51,10 @@
#include <osmocom/msc/signal.h>
#include <osmocom/msc/db.h>
#include <osmocom/msc/transaction.h>
-#include <osmocom/msc/msc_ifaces.h>
#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/paging.h>
#ifdef BUILD_SMPP
#include "smpp_smsc.h"
@@ -61,7 +63,6 @@
void *tall_gsms_ctx;
static uint32_t new_callref = 0x40000001;
-
struct gsm_sms *sms_alloc(void)
{
return talloc_zero(tall_gsms_ctx, struct gsm_sms);
@@ -124,57 +125,39 @@ static int gsm411_sendmsg(struct gsm_trans *trans, struct msgb *msg)
{
LOG_TRANS(trans, LOGL_DEBUG, "GSM4.11 TX %s\n", msgb_hexdump(msg));
msg->l3h = msg->data;
- return msc_tx_dtap(trans->conn, msg);
+ return msc_a_tx_dtap_to_i(trans->msc_a, msg);
}
/* Paging callback for MT SMS (Paging is triggered by SMC) */
-static int paging_cb_mmsms_est_req(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *_conn, void *_trans)
+static void mmsms_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans)
{
- struct ran_conn *conn = _conn;
- struct gsm_trans *trans = _trans;
struct gsm_sms *sms = trans->sms.sms;
- int rc = 0;
- LOG_TRANS(trans, LOGL_DEBUG, "%s(%s)\n", __func__, event == GSM_PAGING_SUCCEEDED ? "success" : "expired");
-
- if (hooknum != GSM_HOOK_RR_PAGING)
- return -EINVAL;
+ LOG_TRANS(trans, LOGL_DEBUG, "%s(%s)\n", __func__, msc_a ? "success" : "expired");
- /* Paging procedure has finished */
- trans->paging_request = NULL;
-
- switch (event) {
- case GSM_PAGING_SUCCEEDED:
+ if (msc_a) {
+ /* Paging succeeded */
/* Associate transaction with established connection */
- trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_SMS);
+ msc_a_get(msc_a, MSC_A_USE_SMS);
+ trans->msc_a = msc_a;
/* Confirm successful connection establishment */
- gsm411_smc_recv(&trans->sms.smc_inst,
- GSM411_MMSMS_EST_CNF, NULL, 0);
- break;
- case GSM_PAGING_EXPIRED:
- case GSM_PAGING_BUSY:
+ gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_EST_CNF, NULL, 0);
+ } else {
+ /* Paging expired or failed */
/* Inform SMC about channel establishment failure */
- gsm411_smc_recv(&trans->sms.smc_inst,
- GSM411_MMSMS_REL_IND, NULL, 0);
+ gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_REL_IND, NULL, 0);
/* gsm411_send_rp_data() doesn't set trans->sms.sms */
if (sms != NULL) {
/* Notify the SMSqueue and free stored SMS */
- send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event);
+ send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
trans->sms.sms = NULL;
sms_free(sms);
}
/* Destroy this transaction */
trans_free(trans);
- rc = -ETIMEDOUT;
- break;
- default:
- LOGP(DLSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event);
}
-
- return rc;
}
static int gsm411_mmsms_est_req(struct gsm_trans *trans)
@@ -183,7 +166,7 @@ static int gsm411_mmsms_est_req(struct gsm_trans *trans)
OSMO_ASSERT(trans->vsub != NULL);
/* Check if connection is already established */
- if (trans->conn != NULL) {
+ if (trans->msc_a != NULL) {
LOG_TRANS(trans, LOGL_DEBUG, "Using an existing connection\n");
return gsm411_smc_recv(&trans->sms.smc_inst,
GSM411_MMSMS_EST_CNF, NULL, 0);
@@ -191,15 +174,12 @@ static int gsm411_mmsms_est_req(struct gsm_trans *trans)
/* Initiate Paging procedure */
LOG_TRANS(trans, LOGL_DEBUG, "Initiating Paging due to MMSMS_EST_REQ\n");
- trans->paging_request = subscr_request_conn(trans->vsub,
- paging_cb_mmsms_est_req,
- trans, "MT SMS",
- SGSAP_SERV_IND_SMS);
+ trans->paging_request = paging_request_start(trans->vsub, PAGING_CAUSE_SIGNALLING_LOW_PRIO,
+ mmsms_paging_cb, trans, "MT-SMS");
if (!trans->paging_request) {
LOG_TRANS(trans, LOGL_ERROR, "Failed to initiate Paging\n");
/* Inform SMC about channel establishment failure */
- gsm411_smc_recv(&trans->sms.smc_inst,
- GSM411_MMSMS_REL_IND, NULL, 0);
+ gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_REL_IND, NULL, 0);
trans_free(trans);
return -EIO;
}
@@ -215,7 +195,7 @@ static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
/* Outgoing needs the highest bit set */
- gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
+ gh->proto_discr = GSM48_PDISC_SMS | (trans->transaction_id<<4);
gh->msg_type = msg_type;
OMSC_LINKID_CB(msg) = trans->dlci;
@@ -408,19 +388,18 @@ static int gsm340_gen_sms_status_report_tpdu(struct gsm_trans *trans, struct msg
static int sms_route_mt_sms(struct gsm_trans *trans, struct gsm_sms *gsms)
{
int rc;
- struct ran_conn *conn = trans->conn;
+ struct msc_a *msc_a = trans->msc_a;
+ struct gsm_network *net = msc_a_net(msc_a);
#ifdef BUILD_SMPP
- int smpp_first = smpp_route_smpp_first(gsms, conn);
-
/*
* Route through SMPP first before going to the local database. In case
* of a unroutable message and no local subscriber, SMPP will be tried
* twice. In case of an unknown subscriber continue with the normal
* delivery of the SMS.
*/
- if (smpp_first) {
- rc = smpp_try_deliver(gsms, conn);
+ if (smpp_route_smpp_first()) {
+ rc = smpp_try_deliver(gsms, msc_a);
if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED)
/* unknown subscriber, try local */
goto try_local;
@@ -428,8 +407,7 @@ static int sms_route_mt_sms(struct gsm_trans *trans, struct gsm_sms *gsms)
LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc);
rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
/* rc will be logged by gsm411_send_rp_error() */
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[
- MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
}
return rc;
}
@@ -438,28 +416,27 @@ try_local:
#endif
/* determine gsms->receiver based on dialled number */
- gsms->receiver = vlr_subscr_find_by_msisdn(conn->network->vlr, gsms->dst.addr, VSUB_USE_SMS_RECEIVER);
+ gsms->receiver = vlr_subscr_find_by_msisdn(net->vlr, gsms->dst.addr, VSUB_USE_SMS_RECEIVER);
if (!gsms->receiver) {
#ifdef BUILD_SMPP
/* Avoid a second look-up */
- if (smpp_first) {
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+ if (smpp_route_smpp_first()) {
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
}
- rc = smpp_try_deliver(gsms, conn);
+ rc = smpp_try_deliver(gsms, msc_a);
if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) {
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
} else if (rc < 0) {
LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc);
rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
/* rc will be logged by gsm411_send_rp_error() */
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[
- MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
}
#else
rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
#endif
} else
rc = 0;
@@ -473,7 +450,6 @@ try_local:
static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
uint32_t gsm411_msg_ref)
{
- struct ran_conn *conn = trans->conn;
uint8_t *smsp = msgb_sms(msg);
struct gsm_sms *gsms;
unsigned int sms_alphabet;
@@ -482,8 +458,14 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
uint8_t da_len_bytes;
uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
int rc = 0;
+ struct msc_a *msc_a = trans->msc_a;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]);
+
+ if (!msc_a || !vsub)
+ return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
gsms = sms_alloc();
if (!gsms)
@@ -579,7 +561,7 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
}
}
- OSMO_STRLCPY_ARRAY(gsms->src.addr, conn->vsub->msisdn);
+ OSMO_STRLCPY_ARRAY(gsms->src.addr, vsub->msisdn);
LOG_TRANS(trans, LOGL_INFO,
"MO SMS -- MTI: 0x%02x, VPF: 0x%02x, "
@@ -593,9 +575,6 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
- /* FIXME: This looks very wrong */
- send_signal(0, NULL, gsms, 0);
-
rc = sms_route_mt_sms(trans, gsms);
/*
@@ -824,7 +803,8 @@ static int gsm411_rx_rp_ack(struct gsm_trans *trans,
static int gsm411_rx_rp_error(struct gsm_trans *trans,
struct gsm411_rp_hdr *rph)
{
- struct gsm_network *net = trans->conn->network;
+ struct msc_a *msc_a = trans->msc_a;
+ struct gsm_network *net = msc_a_net(msc_a);
struct gsm_sms *sms = trans->sms.sms;
uint8_t cause_len = rph->data[0];
uint8_t cause = rph->data[1];
@@ -1005,11 +985,11 @@ static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type,
return rc;
}
-static struct gsm_trans *gsm411_trans_init(struct gsm_network *net, struct vlr_subscr *vsub, struct ran_conn *conn,
- uint8_t tid)
+static struct gsm_trans *gsm411_trans_init(struct gsm_network *net, struct vlr_subscr *vsub, struct msc_a *msc_a,
+ uint8_t tid, bool mo)
{
/* Allocate a new transaction */
- struct gsm_trans *trans = trans_alloc(net, vsub, GSM48_PDISC_SMS, tid, new_callref++);
+ struct gsm_trans *trans = trans_alloc(net, vsub, TRANS_SMS, tid, new_callref++);
if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, "No memory for transaction\n");
return NULL;
@@ -1019,9 +999,24 @@ static struct gsm_trans *gsm411_trans_init(struct gsm_network *net, struct vlr_s
gsm411_smc_init(&trans->sms.smc_inst, 0, 1, gsm411_mn_recv, gsm411_mm_send);
gsm411_smr_init(&trans->sms.smr_inst, 0, 1, gsm411_rl_recv, gsm411_mn_send);
- /* Associate transaction with connection */
- if (conn)
- trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_SMS);
+ if (msc_a) {
+ msc_a_get(msc_a, MSC_A_USE_SMS);
+ trans->msc_a = msc_a;
+
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans);
+ if (mo) {
+ if (!osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_SMS))
+ LOG_TRANS(trans, LOGL_ERROR, "MO SMS without prior CM Service Request\n");
+ else
+ msc_a_put(msc_a, MSC_A_USE_CM_SERVICE_SMS);
+ }
+ }
+
+ /* Init both SMC and SMR state machines */
+ gsm411_smc_init(&trans->sms.smc_inst, 0, 1,
+ gsm411_mn_recv, gsm411_mm_send);
+ gsm411_smr_init(&trans->sms.smr_inst, 0, 1,
+ gsm411_rl_recv, gsm411_mn_send);
return trans;
}
@@ -1053,22 +1048,22 @@ static int gsm411_assign_sm_rp_mr(struct gsm_trans *trans)
static struct gsm_trans *gsm411_alloc_mt_trans(struct gsm_network *net,
struct vlr_subscr *vsub)
{
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct gsm_trans *trans = NULL;
int tid;
/* Generate a new transaction ID */
- tid = trans_assign_trans_id(net, vsub, GSM48_PDISC_SMS);
+ tid = trans_assign_trans_id(net, vsub, TRANS_SMS);
if (tid == -1) {
LOG_TRANS(trans, LOGL_ERROR, "No available transaction IDs\n");
return NULL;
}
/* Attempt to find an existing connection */
- conn = connection_for_subscr(vsub);
+ msc_a = msc_a_for_vsub(vsub, true);
/* Allocate a new transaction */
- trans = gsm411_trans_init(net, vsub, conn, tid);
+ trans = gsm411_trans_init(net, vsub, msc_a, tid, false);
if (!trans)
return NULL;
@@ -1197,8 +1192,7 @@ int gsm411_send_rp_data(struct gsm_network *net, struct vlr_subscr *vsub,
}
/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
-int gsm0411_rcv_sms(struct ran_conn *conn,
- struct msgb *msg)
+int gsm0411_rcv_sms(struct msc_a *msc_a, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t msg_type = gh->msg_type;
@@ -1207,12 +1201,10 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
struct gsm_trans *trans;
int new_trans = 0;
int rc = 0;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm_network *net = msc_a_net(msc_a);
- if (!conn->vsub)
- return -EIO;
- /* FIXME: send some error message */
-
- trans = trans_find_by_id(conn, GSM48_PDISC_SMS, transaction_id);
+ trans = trans_find_by_id(msc_a, TRANS_SMS, transaction_id);
/*
* A transaction we created but don't know about?
@@ -1226,7 +1218,8 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
}
if (!trans) {
- trans = gsm411_trans_init(conn->network, conn->vsub, conn, transaction_id);
+ new_trans = 1;
+ trans = gsm411_trans_init(net, vsub, msc_a, transaction_id, true);
if (!trans) {
/* FIXME: send some error message */
return -ENOMEM;
@@ -1234,9 +1227,6 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
trans->sms.sm_rp_mr = rph->msg_ref; /* SM-RP Message Reference */
trans->dlci = OMSC_LINKID_CB(msg); /* DLCI as received from BSC */
-
- new_trans = 1;
- cm_service_request_concludes(conn, msg);
}
LOG_TRANS(trans, LOGL_DEBUG, "receiving SMS message %s\n",
@@ -1257,7 +1247,7 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
if (i == transaction_id)
continue;
- ptrans = trans_find_by_id(conn, GSM48_PDISC_SMS, i);
+ ptrans = trans_find_by_id(msc_a, TRANS_SMS, i);
if (!ptrans)
continue;
@@ -1268,8 +1258,6 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
}
}
- ran_conn_communicating(conn);
-
gsm411_smc_recv(&trans->sms.smc_inst,
(new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND,
msg, msg_type);
@@ -1297,19 +1285,19 @@ void _gsm411_sms_trans_free(struct gsm_trans *trans)
}
/* Process incoming SAPI N-REJECT from BSC */
-void gsm411_sapi_n_reject(struct ran_conn *conn)
+void gsm411_sapi_n_reject(struct msc_a *msc_a)
{
struct gsm_network *net;
struct gsm_trans *trans, *tmp;
- net = conn->network;
+ net = msc_a_net(msc_a);
llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry) {
struct gsm_sms *sms;
- if (trans->conn != conn)
+ if (trans->msc_a != msc_a)
continue;
- if (trans->protocol != GSM48_PDISC_SMS)
+ if (trans->type != TRANS_SMS)
continue;
sms = trans->sms.sms;
diff --git a/src/libmsc/gsm_04_11_gsup.c b/src/libmsc/gsm_04_11_gsup.c
index cd83b4102..30f18714d 100644
--- a/src/libmsc/gsm_04_11_gsup.c
+++ b/src/libmsc/gsm_04_11_gsup.c
@@ -31,6 +31,8 @@
#include <osmocom/msc/msc_common.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/gsup_client_mux.h>
/* Common helper for preparing to be encoded GSUP message */
static void gsup_sm_msg_init(struct osmo_gsup_message *gsup_msg,
@@ -38,11 +40,11 @@ static void gsup_sm_msg_init(struct osmo_gsup_message *gsup_msg,
uint8_t *sm_rp_mr)
{
/* Init a mew GSUP message */
- memset(gsup_msg, 0x00, sizeof(*gsup_msg));
- gsup_msg->message_type = msg_type;
-
- /* SM-RP-MR (Message Reference) */
- gsup_msg->sm_rp_mr = sm_rp_mr;
+ *gsup_msg = (struct osmo_gsup_message){
+ .message_type = msg_type,
+ .sm_rp_mr = sm_rp_mr,
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_SMS,
+ };
/* Fill in subscriber's IMSI */
OSMO_STRLCPY_ARRAY(gsup_msg->imsi, imsi);
@@ -89,7 +91,7 @@ int gsm411_gsup_mo_fwd_sm_req(struct gsm_trans *trans, struct msgb *msg,
gsup_msg.sm_rp_ui_len = msgb_l4len(msg);
gsup_msg.sm_rp_ui = (uint8_t *) msgb_sms(msg);
- return osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
+ return gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
}
int gsm411_gsup_mo_ready_for_sm_req(struct gsm_trans *trans, uint8_t sm_rp_mr)
@@ -111,12 +113,12 @@ int gsm411_gsup_mo_ready_for_sm_req(struct gsm_trans *trans, uint8_t sm_rp_mr)
/* Indicate SMMA as the Alert Reason */
gsup_msg.sm_alert_rsn = OSMO_GSUP_SMS_SM_ALERT_RSN_MEM_AVAIL;
- return osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
+ return gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
}
/* Triggers either RP-ACK or RP-ERROR on response from SMSC */
-int gsm411_gsup_mo_handler(struct vlr_subscr *vsub,
- struct osmo_gsup_message *gsup_msg)
+static int gsm411_gsup_mo_handler(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup_msg)
{
struct vlr_instance *vlr;
struct gsm_network *net;
@@ -203,7 +205,7 @@ int gsm411_gsup_mt_fwd_sm_res(struct gsm_trans *trans, uint8_t sm_rp_mr)
gsup_sm_msg_init(&gsup_msg, OSMO_GSUP_MSGT_MT_FORWARD_SM_RESULT,
trans->vsub->imsi, &sm_rp_mr);
- return osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
+ return gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
}
int gsm411_gsup_mt_fwd_sm_err(struct gsm_trans *trans,
@@ -224,12 +226,12 @@ int gsm411_gsup_mt_fwd_sm_err(struct gsm_trans *trans,
gsup_msg.sm_rp_cause = &cause;
/* TODO: include optional SM-RP-UI field if present */
- return osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
+ return gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
}
/* Handles MT SMS (and triggers Paging Request if required) */
-int gsm411_gsup_mt_handler(struct vlr_subscr *vsub,
- struct osmo_gsup_message *gsup_msg)
+static int gsm411_gsup_mt_handler(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup_msg)
{
struct vlr_instance *vlr;
struct gsm_network *net;
@@ -285,3 +287,34 @@ msg_error:
LOGP(DLSMS, LOGL_NOTICE, "RX malformed MT-forwardSM-Req\n");
return -EINVAL;
}
+
+int gsm411_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg)
+{
+ struct vlr_instance *vlr = data;
+ struct vlr_subscr *vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__);
+
+ if (!vsub) {
+ gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_IMSI_UNKNOWN);
+ return -GMM_CAUSE_IMSI_UNKNOWN;
+ }
+
+ switch (gsup_msg->message_type) {
+ /* GSM 04.11 code implementing MO SMS */
+ case OSMO_GSUP_MSGT_MO_FORWARD_SM_ERROR:
+ case OSMO_GSUP_MSGT_MO_FORWARD_SM_RESULT:
+ case OSMO_GSUP_MSGT_READY_FOR_SM_ERROR:
+ case OSMO_GSUP_MSGT_READY_FOR_SM_RESULT:
+ DEBUGP(DMSC, "Routed to GSM 04.11 MO handler\n");
+ return gsm411_gsup_mo_handler(vsub, gsup_msg);
+
+ /* GSM 04.11 code implementing MT SMS */
+ case OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST:
+ DEBUGP(DMSC, "Routed to GSM 04.11 MT handler\n");
+ return gsm411_gsup_mt_handler(vsub, gsup_msg);
+
+ default:
+ LOGP(DMM, LOGL_ERROR, "No handler found for %s, dropping message...\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ return -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+ }
+}
diff --git a/src/libmsc/gsm_04_14.c b/src/libmsc/gsm_04_14.c
index 8fe03a88b..044b61c0b 100644
--- a/src/libmsc/gsm_04_14.c
+++ b/src/libmsc/gsm_04_14.c
@@ -30,7 +30,7 @@
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsm_subscriber.h>
#include <osmocom/msc/gsm_04_08.h>
-#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/msc_a.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm_utils.h>
@@ -51,21 +51,21 @@ static struct msgb *create_gsm0414_msg(uint8_t msg_type)
return msg;
}
-static int gsm0414_conn_sendmsg(struct ran_conn *conn, struct msgb *msg)
+static int gsm0414_conn_sendmsg(struct msc_a *msc_a, struct msgb *msg)
{
- return msc_tx_dtap(conn, msg);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
-static int gsm0414_tx_simple(struct ran_conn *conn, uint8_t msg_type)
+static int gsm0414_tx_simple(struct msc_a *msc_a, uint8_t msg_type)
{
struct msgb *msg = create_gsm0414_msg(msg_type);
- return gsm0414_conn_sendmsg(conn, msg);
+ return gsm0414_conn_sendmsg(msc_a, msg);
}
/* Send a CLOSE_TCH_LOOOP_CMD according to Section 8.1 */
-int gsm0414_tx_close_tch_loop_cmd(struct ran_conn *conn,
+int gsm0414_tx_close_tch_loop_cmd(struct msc_a *msc_a,
enum gsm414_tch_loop_mode loop_mode)
{
struct msgb *msg = create_gsm0414_msg(GSM414_MT_CLOSE_TCH_LOOP_CMD);
@@ -74,49 +74,49 @@ int gsm0414_tx_close_tch_loop_cmd(struct ran_conn *conn,
subch = (loop_mode << 1);
msgb_put_u8(msg, subch);
- return gsm0414_conn_sendmsg(conn, msg);
+ return gsm0414_conn_sendmsg(msc_a, msg);
}
/* Send a OPEN_LOOP_CMD according to Section 8.3 */
-int gsm0414_tx_open_loop_cmd(struct ran_conn *conn)
+int gsm0414_tx_open_loop_cmd(struct msc_a *msc_a)
{
- return gsm0414_tx_simple(conn, GSM414_MT_OPEN_LOOP_CMD);
+ return gsm0414_tx_simple(msc_a, GSM414_MT_OPEN_LOOP_CMD);
}
/* Send a ACT_EMMI_CMD according to Section 8.8 */
-int gsm0414_tx_act_emmi_cmd(struct ran_conn *conn)
+int gsm0414_tx_act_emmi_cmd(struct msc_a *msc_a)
{
- return gsm0414_tx_simple(conn, GSM414_MT_ACT_EMMI_CMD);
+ return gsm0414_tx_simple(msc_a, GSM414_MT_ACT_EMMI_CMD);
}
/* Send a DEACT_EMMI_CMD according to Section 8.10 */
-int gsm0414_tx_deact_emmi_cmd(struct ran_conn *conn)
+int gsm0414_tx_deact_emmi_cmd(struct msc_a *msc_a)
{
- return gsm0414_tx_simple(conn, GSM414_MT_DEACT_EMMI_CMD);
+ return gsm0414_tx_simple(msc_a, GSM414_MT_DEACT_EMMI_CMD);
}
/* Send a TEST_INTERFACE according to Section 8.11 */
-int gsm0414_tx_test_interface(struct ran_conn *conn,
+int gsm0414_tx_test_interface(struct msc_a *msc_a,
uint8_t tested_devs)
{
struct msgb *msg = create_gsm0414_msg(GSM414_MT_TEST_INTERFACE);
msgb_put_u8(msg, tested_devs);
- return gsm0414_conn_sendmsg(conn, msg);
+ return gsm0414_conn_sendmsg(msc_a, msg);
}
/* Send a RESET_MS_POSITION_STORED according to Section 8.11 */
-int gsm0414_tx_reset_ms_pos_store(struct ran_conn *conn,
+int gsm0414_tx_reset_ms_pos_store(struct msc_a *msc_a,
uint8_t technology)
{
struct msgb *msg = create_gsm0414_msg(GSM414_MT_RESET_MS_POS_STORED);
msgb_put_u8(msg, technology);
- return gsm0414_conn_sendmsg(conn, msg);
+ return gsm0414_conn_sendmsg(msc_a, msg);
}
/* Entry point for incoming GSM48_PDISC_TEST received from MS */
-int gsm0414_rcv_test(struct ran_conn *conn,
+int gsm0414_rcv_test(struct msc_a *msc_a,
struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c
index e3547f41d..6a79b5bb6 100644
--- a/src/libmsc/gsm_04_80.c
+++ b/src/libmsc/gsm_04_80.c
@@ -26,7 +26,7 @@
#include <errno.h>
#include <osmocom/msc/gsm_04_80.h>
-#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/msc_a.h>
#include <osmocom/gsm/protocol/gsm_04_80.h>
#include <osmocom/gsm/gsm0480.h>
@@ -36,7 +36,7 @@
/*! Send a MT RELEASE COMPLETE message with Reject component
* (see section 3.6.1) and given error code (see section 3.6.7).
*
- * \param[in] conn Active RAN connection
+ * \param[in] msc_a Active subscriber
* \param[in] transaction_id Transaction ID with TI flag set
* \param[in] invoke_id InvokeID of the request
* \param[in] problem_tag Problem code tag (table 3.13)
@@ -47,9 +47,8 @@
* failed, any incorrect value can be passed (0x00 > x > 0xff), so
* the universal NULL-tag (see table 3.6) will be used instead.
*/
-int msc_send_ussd_reject(struct ran_conn *conn,
- uint8_t transaction_id, int invoke_id,
- uint8_t problem_tag, uint8_t problem_code)
+int msc_send_ussd_reject(struct msc_a *msc_a, uint8_t transaction_id, int invoke_id,
+ uint8_t problem_tag, uint8_t problem_code)
{
struct gsm48_hdr *gh;
struct msgb *msg;
@@ -67,27 +66,26 @@ int msc_send_ussd_reject(struct ran_conn *conn,
gh->proto_discr |= transaction_id << 4;
gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
- return msc_tx_dtap(conn, msg);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
-int msc_send_ussd_notify(struct ran_conn *conn, int level, const char *text)
+int msc_send_ussd_notify(struct msc_a *msc_a, int level, const char *text)
{
struct msgb *msg = gsm0480_create_ussd_notify(level, text);
if (!msg)
return -1;
- return msc_tx_dtap(conn, msg);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
-int msc_send_ussd_release_complete(struct ran_conn *conn,
- uint8_t transaction_id)
+int msc_send_ussd_release_complete(struct msc_a *msc_a, uint8_t transaction_id)
{
struct msgb *msg = gsm0480_create_release_complete(transaction_id);
if (!msg)
return -1;
- return msc_tx_dtap(conn, msg);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
-int msc_send_ussd_release_complete_cause(struct ran_conn *conn,
+int msc_send_ussd_release_complete_cause(struct msc_a *msc_a,
uint8_t transaction_id,
uint8_t cause_loc, uint8_t cause_val)
{
@@ -112,5 +110,5 @@ int msc_send_ussd_release_complete_cause(struct ran_conn *conn,
cause_ie[2] = (1 << 7) | (0x03 << 5) | (cause_loc & 0x0f);
cause_ie[3] = (1 << 7) | cause_val;
- return msc_tx_dtap(conn, msg);
+ return msc_a_tx_dtap_to_i(msc_a, msg);
}
diff --git a/src/libmsc/gsm_09_11.c b/src/libmsc/gsm_09_11.c
index 25fe4aa83..984cc53ca 100644
--- a/src/libmsc/gsm_09_11.c
+++ b/src/libmsc/gsm_09_11.c
@@ -46,7 +46,10 @@
#include <osmocom/msc/gsm_04_08.h>
#include <osmocom/msc/transaction.h>
#include <osmocom/gsupclient/gsup_client.h>
-#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/paging.h>
+#include <osmocom/msc/gsup_client_mux.h>
/* FIXME: choose a proper range */
static uint32_t new_callref = 0x20000001;
@@ -63,50 +66,65 @@ static void ncss_session_timeout_handler(void *_trans)
LOG_TRANS(trans, LOGL_NOTICE, "SS/USSD session timeout, releasing\n");
/* Indicate connection release to subscriber (if active) */
- if (trans->conn != NULL) {
+ if (trans->msc_a != NULL) {
/* This pair of cause location and value is used by commercial networks */
- msc_send_ussd_release_complete_cause(trans->conn, trans->transaction_id,
+ msc_send_ussd_release_complete_cause(trans->msc_a, trans->transaction_id,
GSM48_CAUSE_LOC_PUN_S_LU, GSM48_CC_CAUSE_NORMAL_UNSPEC);
}
/* Terminate GSUP session with EUSE */
- gsup_msg.message_type = OSMO_GSUP_MSGT_PROC_SS_ERROR;
- OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi);
+ gsup_msg = (struct osmo_gsup_message){
+ .message_type = OSMO_GSUP_MSGT_PROC_SS_ERROR,
+
+ .session_state = OSMO_GSUP_SESSION_STATE_END,
+ .session_id = trans->callref,
+ .cause = GMM_CAUSE_NET_FAIL,
+
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_USSD,
+ };
- gsup_msg.session_state = OSMO_GSUP_SESSION_STATE_END;
- gsup_msg.session_id = trans->callref;
- gsup_msg.cause = GMM_CAUSE_NET_FAIL;
+ OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi);
- osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
+ gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
/* Finally, release this transaction */
trans_free(trans);
}
/* Entry point for call independent MO SS messages */
-int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
+int gsm0911_rcv_nc_ss(struct msc_a *msc_a, struct msgb *msg)
{
+ struct gsm_network *net;
+ struct vlr_subscr *vsub;
struct gsm48_hdr *gh = msgb_l3(msg);
struct osmo_gsup_message gsup_msg;
struct gsm_trans *trans;
- struct msgb *gsup_msgb;
uint16_t facility_ie_len;
uint8_t *facility_ie;
uint8_t tid;
uint8_t msg_type;
int rc;
+ net = msc_a_net(msc_a);
+ OSMO_ASSERT(net);
+
+ vsub = msc_a_vsub(msc_a);
+ if (!vsub) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "No vlr_subscr set for this conn\n");
+ return -EINVAL;
+ }
+
msg_type = gsm48_hdr_msg_type(gh);
tid = gsm48_hdr_trans_id_flip_ti(gh);
/* Associate logging messages with this subscriber */
- log_set_context(LOG_CTX_VLR_SUBSCR, conn->vsub);
+ log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
/* Reuse existing transaction, or create a new one */
- trans = trans_find_by_id(conn, GSM48_PDISC_NC_SS, tid);
+ trans = trans_find_by_id(msc_a, TRANS_USSD, tid);
if (!trans) {
/* Count MS-initiated attempts to establish a NC SS/USSD session */
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_REQUESTS]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_REQUESTS]);
/**
* According to GSM TS 04.80, section 2.4.2 "Register
@@ -119,17 +137,16 @@ int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
if (msg_type != GSM0480_MTYPE_REGISTER) {
LOG_TRANS(trans, LOGL_ERROR, "Rx wrong SS/USSD message type for new transaction: %s\n",
gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type));
- gsm48_tx_simple(conn,
+ gsm48_tx_simple(msc_a,
GSM48_PDISC_NC_SS | (tid << 4),
GSM0480_MTYPE_RELEASE_COMPLETE);
return -EINVAL;
}
- trans = trans_alloc(conn->network, conn->vsub,
- GSM48_PDISC_NC_SS, tid, new_callref++);
+ trans = trans_alloc(net, vsub, TRANS_USSD, tid, new_callref++);
if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, " -> No memory for trans\n");
- gsm48_tx_simple(conn,
+ gsm48_tx_simple(msc_a,
GSM48_PDISC_NC_SS | (tid << 4),
GSM0480_MTYPE_RELEASE_COMPLETE);
return -ENOMEM;
@@ -140,20 +157,28 @@ int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
ncss_session_timeout_handler, trans);
/* Count active NC SS/USSD sessions */
- osmo_counter_inc(conn->network->active_nc_ss);
+ osmo_counter_inc(net->active_nc_ss);
- trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS);
trans->dlci = OMSC_LINKID_CB(msg);
- cm_service_request_concludes(conn, msg);
+ trans->msc_a = msc_a;
+ msc_a_get(msc_a, MSC_A_USE_NC_SS);
+
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans);
+
+ /* An earlier CM Service Request for this SS message now has concluded */
+ if (!osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_SS))
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Creating new MO SS transaction without prior CM Service Request\n");
+ else
+ msc_a_put(msc_a, MSC_A_USE_CM_SERVICE_SS);
}
LOG_TRANS(trans, LOGL_DEBUG, "Received SS/USSD msg %s\n",
gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type));
/* (Re)schedule the inactivity timer */
- if (conn->network->ncss_guard_timeout > 0) {
- osmo_timer_schedule(&trans->ss.timer_guard,
- conn->network->ncss_guard_timeout, 0);
+ if (net->ncss_guard_timeout > 0) {
+ osmo_timer_schedule(&trans->ss.timer_guard, net->ncss_guard_timeout, 0);
}
/* Attempt to extract Facility IE */
@@ -175,9 +200,11 @@ int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
}
/* Compose a mew GSUP message */
- memset(&gsup_msg, 0x00, sizeof(gsup_msg));
- gsup_msg.message_type = OSMO_GSUP_MSGT_PROC_SS_REQUEST;
- gsup_msg.session_id = trans->callref;
+ gsup_msg = (struct osmo_gsup_message){
+ .message_type = OSMO_GSUP_MSGT_PROC_SS_REQUEST,
+ .session_id = trans->callref,
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_USSD,
+ };
/**
* Perform A-interface to GSUP-interface mapping,
@@ -202,45 +229,23 @@ int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
}
/* Fill in subscriber's IMSI */
- OSMO_STRLCPY_ARRAY(gsup_msg.imsi, conn->vsub->imsi);
+ OSMO_STRLCPY_ARRAY(gsup_msg.imsi, vsub->imsi);
- /* Allocate GSUP message buffer */
- gsup_msgb = osmo_gsup_client_msgb_alloc();
- if (!gsup_msgb) {
- LOG_TRANS(trans, LOGL_ERROR, "Couldn't allocate GSUP message\n");
- rc = -ENOMEM;
- goto error;
- }
-
- /* Encode GSUP message */
- rc = osmo_gsup_encode(gsup_msgb, &gsup_msg);
- if (rc) {
- LOG_TRANS(trans, LOGL_ERROR, "Couldn't encode GSUP message\n");
- goto error;
- }
-
- /* Finally send */
- rc = osmo_gsup_client_send(conn->network->vlr->gsup_client, gsup_msgb);
- if (rc) {
- LOG_TRANS(trans, LOGL_ERROR, "Couldn't send GSUP message\n");
- goto error;
- }
+ rc = gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
/* Should we release connection? Or wait for response? */
if (msg_type == GSM0480_MTYPE_RELEASE_COMPLETE)
trans_free(trans);
- else
- ran_conn_communicating(conn);
/* Count established MS-initiated NC SS/USSD sessions */
if (msg_type == GSM0480_MTYPE_REGISTER)
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_ESTABLISHED]);
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_ESTABLISHED]);
return 0;
error:
/* Abort transaction on DTAP-interface */
- msc_send_ussd_reject(conn, tid, -1,
+ msc_send_ussd_reject(msc_a, tid, -1,
GSM_0480_PROBLEM_CODE_TAG_GENERAL,
GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
if (trans)
@@ -251,76 +256,69 @@ error:
}
/* Call-back from paging the B-end of the connection */
-static int handle_paging_event(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *_conn, void *_transt)
+static void ss_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans)
{
- struct ran_conn *conn = _conn;
- enum gsm_paging_event paging_event = event;
- struct gsm_trans *transt = _transt;
struct gsm48_hdr *gh;
struct msgb *ss_msg;
- OSMO_ASSERT(!transt->conn);
- OSMO_ASSERT(transt->ss.msg);
+ if (trans->msc_a) {
+ LOG_MSC_A_CAT(msc_a, DPAG, LOGL_ERROR,
+ "Handle paging error: transaction already associated with subsciber,"
+ " apparently it was already handled. Skip.\n");
+ return;
+ }
+ OSMO_ASSERT(trans->ss.msg);
- switch (paging_event) {
- case GSM_PAGING_SUCCEEDED:
- DEBUGP(DMM, "Paging subscr %s succeeded!\n",
- vlr_subscr_msisdn_or_name(transt->vsub));
+ if (msc_a) {
+ struct gsm_network *net = msc_a_net(msc_a);
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "Paging succeeded\n");
/* Assign connection */
- transt->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS);
- transt->paging_request = NULL;
+ msc_a_get(msc_a, MSC_A_USE_NC_SS);
+ trans->msc_a = msc_a;
+ trans->paging_request = NULL;
/* (Re)schedule the inactivity timer */
- if (conn->network->ncss_guard_timeout > 0) {
- osmo_timer_schedule(&transt->ss.timer_guard,
- conn->network->ncss_guard_timeout, 0);
+ if (net->ncss_guard_timeout > 0) {
+ osmo_timer_schedule(&trans->ss.timer_guard, net->ncss_guard_timeout, 0);
}
/* Send stored message */
- ss_msg = transt->ss.msg;
+ ss_msg = trans->ss.msg;
gh = (struct gsm48_hdr *) msgb_push(ss_msg, sizeof(*gh));
gh->proto_discr = GSM48_PDISC_NC_SS;
- gh->proto_discr |= transt->transaction_id << 4;
+ gh->proto_discr |= trans->transaction_id << 4;
gh->msg_type = GSM0480_MTYPE_REGISTER;
/* Sent to the MS, give ownership of ss_msg */
- msc_tx_dtap(transt->conn, ss_msg);
- transt->ss.msg = NULL;
+ msc_a_tx_dtap_to_i(msc_a, ss_msg);
+ trans->ss.msg = NULL;
/* Count established network-initiated NC SS/USSD sessions */
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]);
- break;
- case GSM_PAGING_EXPIRED:
- case GSM_PAGING_BUSY:
- DEBUGP(DMM, "Paging subscr %s %s!\n",
- vlr_subscr_msisdn_or_name(transt->vsub),
- paging_event == GSM_PAGING_EXPIRED ? "expired" : "busy");
+ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]);
+ } else {
+ LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "Paging expired\n");
/* TODO: inform HLR about this failure */
- msgb_free(transt->ss.msg);
- transt->ss.msg = NULL;
+ msgb_free(trans->ss.msg);
+ trans->ss.msg = NULL;
- transt->callref = 0;
- transt->paging_request = NULL;
- trans_free(transt);
- break;
+ trans->callref = 0;
+ trans->paging_request = NULL;
+ trans_free(trans);
}
-
- return 0;
}
static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net,
- struct vlr_subscr *vsub, struct osmo_gsup_message *gsup_msg)
+ struct vlr_subscr *vsub, const struct osmo_gsup_message *gsup_msg)
{
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct gsm_trans *trans, *transt;
int tid;
/* Allocate transaction first, for log context */
- trans = trans_alloc(net, vsub, GSM48_PDISC_NC_SS,
+ trans = trans_alloc(net, vsub, TRANS_USSD,
TRANS_ID_UNASSIGNED, gsup_msg->session_id);
if (!trans) {
@@ -355,7 +353,7 @@ static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net,
osmo_counter_inc(net->active_nc_ss);
/* Assign transaction ID */
- tid = trans_assign_trans_id(trans->net, trans->vsub, GSM48_PDISC_NC_SS);
+ tid = trans_assign_trans_id(trans->net, trans->vsub, TRANS_USSD);
if (tid < 0) {
LOG_TRANS(trans, LOGL_ERROR, "No free transaction ID\n");
/* TODO: inform HLR about this */
@@ -371,10 +369,11 @@ static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net,
ncss_session_timeout_handler, trans);
/* Attempt to find connection */
- conn = connection_for_subscr(vsub);
- if (conn) {
+ msc_a = msc_a_for_vsub(vsub, true);
+ if (msc_a) {
/* Assign connection */
- trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS);
+ msc_a_get(msc_a, MSC_A_USE_NC_SS);
+ trans->msc_a = msc_a;
trans->dlci = 0x00; /* SAPI=0, not SACCH */
return trans;
}
@@ -390,13 +389,14 @@ static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net,
LOG_TRANS(trans, LOGL_ERROR, "Paging already started, "
"rejecting message...\n");
trans_free(trans);
+ /* FIXME: WTF IS THIS!? This is completely insane. Presence of a trans doesn't indicate Paging, and even
+ * if, why drop the current request??? */
return NULL;
}
/* Trigger Paging Request */
- trans->paging_request = subscr_request_conn(vsub,
- &handle_paging_event, trans, "GSM 09.11 SS/USSD",
- SGSAP_SERV_IND_CS_CALL);
+ trans->paging_request = paging_request_start(vsub, PAGING_CAUSE_SIGNALLING_HIGH_PRIO,
+ ss_paging_cb, trans, "GSM 09.11 SS/USSD");
if (!trans->paging_request) {
LOG_TRANS(trans, LOGL_ERROR, "Failed to allocate paging token\n");
trans_free(trans);
@@ -431,15 +431,21 @@ void _gsm911_nc_ss_trans_free(struct gsm_trans *trans)
osmo_counter_dec(trans->net->active_nc_ss);
}
-int gsm0911_gsup_handler(struct vlr_subscr *vsub,
- struct osmo_gsup_message *gsup_msg)
+int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg)
{
- struct vlr_instance *vlr;
+ struct vlr_instance *vlr = data;
struct gsm_network *net;
struct gsm_trans *trans;
struct gsm48_hdr *gh;
struct msgb *ss_msg;
bool trans_end;
+ struct msc_a *msc_a;
+ struct vlr_subscr *vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__);
+
+ if (!vsub) {
+ gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_IMSI_UNKNOWN);
+ return -GMM_CAUSE_IMSI_UNKNOWN;
+ }
/* Associate logging messages with this subscriber */
log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
@@ -542,7 +548,13 @@ int gsm0911_gsup_handler(struct vlr_subscr *vsub,
trans_end = (gh->msg_type == GSM0480_MTYPE_RELEASE_COMPLETE);
/* Sent to the MS, give ownership of ss_msg */
- msc_tx_dtap(trans->conn, ss_msg);
+ msc_a = trans->msc_a;
+ if (!msc_a) {
+ LOG_TRANS(trans, LOGL_ERROR, "Cannot send SS message, no local MSC-A role defined for subscriber\n");
+ msgb_free(ss_msg);
+ return -EINVAL;
+ }
+ msc_a_tx_dtap_to_i(msc_a, ss_msg);
/* Release transaction if required */
if (trans_end)
diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c
deleted file mode 100644
index 97b58b236..000000000
--- a/src/libmsc/gsm_subscriber.c
+++ /dev/null
@@ -1,216 +0,0 @@
-/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */
-
-/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
- * (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "../../bscconfig.h"
-
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-#include <time.h>
-#include <stdbool.h>
-
-#include <osmocom/core/talloc.h>
-
-#include <osmocom/vty/vty.h>
-
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#else
-#include <osmocom/msc/iu_dummy.h>
-#endif
-
-#include <osmocom/msc/gsm_subscriber.h>
-#include <osmocom/msc/gsm_04_08.h>
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/signal.h>
-#include <osmocom/msc/db.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/msc_ifaces.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/sgs_iface.h>
-
-#define VSUB_USE_PAGING "Paging"
-
-void subscr_paging_cancel(struct vlr_subscr *vsub, enum gsm_paging_event event)
-{
- subscr_paging_dispatch(GSM_HOOK_RR_PAGING, event, NULL, NULL, vsub);
-}
-
-int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *data, void *param)
-{
- struct subscr_request *request, *tmp;
- struct ran_conn *conn = data;
- struct vlr_subscr *vsub = param;
- struct paging_signal_data sig_data;
-
- OSMO_ASSERT(vsub);
- OSMO_ASSERT(hooknum == GSM_HOOK_RR_PAGING);
- OSMO_ASSERT(!(conn && (conn->vsub != vsub)));
- OSMO_ASSERT(!((event == GSM_PAGING_SUCCEEDED) && !conn));
-
- LOGP(DPAG, LOGL_DEBUG, "Paging %s for %s (event=%d)\n",
- event == GSM_PAGING_SUCCEEDED ? "success" : "failure",
- vlr_subscr_name(vsub), event);
-
- if (!vsub->cs.is_paging) {
- LOGP(DPAG, LOGL_ERROR,
- "Paging Response received for subscriber"
- " that is not paging.\n");
- return -EINVAL;
- }
-
- osmo_timer_del(&vsub->cs.paging_response_timer);
-
- if (event == GSM_PAGING_SUCCEEDED
- || event == GSM_PAGING_EXPIRED)
- msc_stop_paging(vsub);
-
- /* Inform parts of the system we don't know */
- sig_data.vsub = vsub;
- sig_data.conn = conn;
- sig_data.paging_result = event;
- osmo_signal_dispatch(SS_PAGING,
- event == GSM_PAGING_SUCCEEDED ?
- S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
- &sig_data);
-
- llist_for_each_entry_safe(request, tmp, &vsub->cs.requests, entry) {
- llist_del(&request->entry);
- if (request->cbfn) {
- LOGP(DPAG, LOGL_DEBUG, "Calling paging cbfn.\n");
- request->cbfn(hooknum, event, msg, data, request->param);
- } else
- LOGP(DPAG, LOGL_DEBUG, "Paging without action.\n");
- talloc_free(request);
- }
-
- /* balanced with the moment we start paging */
- vsub->cs.is_paging = false;
- vlr_subscr_put(vsub, VSUB_USE_PAGING);
- return 0;
-}
-
-/* Execute a paging on the currently active RAN. Returns the number of
- * delivered paging requests or -EINVAL in case of failure. */
-static int msc_paging_request(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind)
-{
- /* The subscriber was last seen in subscr->lac. Find out which
- * BSCs/RNCs are responsible and send them a paging request via open
- * SCCP connections (if any). */
- switch (vsub->cs.attached_via_ran) {
- case OSMO_RAT_GERAN_A:
- return a_iface_tx_paging(vsub->imsi, vsub->tmsi, vsub->cgi.lai.lac);
- case OSMO_RAT_UTRAN_IU:
- return ranap_iu_page_cs(vsub->imsi,
- vsub->tmsi == GSM_RESERVED_TMSI?
- NULL : &vsub->tmsi,
- vsub->cgi.lai.lac);
- case OSMO_RAT_EUTRAN_SGS:
- return sgs_iface_tx_paging(vsub, serv_ind);
- default:
- break;
- }
-
- LOGP(DPAG, LOGL_ERROR, "%s: Cannot page, subscriber not attached\n",
- vlr_subscr_name(vsub));
- return -EINVAL;
-}
-
-static void paging_response_timer_cb(void *data)
-{
- struct vlr_subscr *vsub = data;
- subscr_paging_cancel(vsub, GSM_PAGING_EXPIRED);
-}
-
-/*! \brief Start a paging request for vsub, call cbfn(param) when done.
- * \param vsub subscriber to page.
- * \param cbfn function to call when the conn is established.
- * \param param caller defined param to pass to cbfn().
- * \param label human readable label of the request kind used for logging.
- * \param serv_ind sgsap service indicator (in case SGs interface is used to page).
- */
-struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub,
- gsm_cbfn *cbfn, void *param,
- const char *label, enum sgsap_service_ind serv_ind)
-{
- int rc;
- struct subscr_request *request;
- struct gsm_network *net = vsub->vlr->user_ctx;
-
- /* Start paging.. we know it is async so we can do it before */
- if (!vsub->cs.is_paging) {
- LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet, start paging.\n",
- vlr_subscr_name(vsub));
- rc = msc_paging_request(vsub, serv_ind);
- if (rc <= 0) {
- LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n",
- vlr_subscr_name(vsub), rc);
- return NULL;
- }
- /* reduced on the first paging callback */
- vlr_subscr_get(vsub, VSUB_USE_PAGING);
- vsub->cs.is_paging = true;
- osmo_timer_setup(&vsub->cs.paging_response_timer, paging_response_timer_cb, vsub);
- osmo_timer_schedule(&vsub->cs.paging_response_timer, net->paging_response_timer, 0);
- } else {
- LOGP(DMM, LOGL_DEBUG, "Subscriber %s already paged.\n",
- vlr_subscr_name(vsub));
- }
-
- /* TODO: Stop paging in case of memory allocation failure */
- request = talloc_zero(vsub, struct subscr_request);
- if (!request)
- return NULL;
-
- request->cbfn = cbfn;
- request->param = param;
- llist_add_tail(&request->entry, &vsub->cs.requests);
- return request;
-}
-
-void subscr_remove_request(struct subscr_request *request)
-{
- llist_del(&request->entry);
- talloc_free(request);
-}
-
-struct ran_conn *connection_for_subscr(struct vlr_subscr *vsub)
-{
- struct gsm_network *net = vsub->vlr->user_ctx;
- struct ran_conn *conn;
-
- llist_for_each_entry(conn, &net->ran_conns, entry) {
- if (conn->vsub != vsub)
- continue;
- /* Found a conn, but is it in a usable state? Must not add transactions to a conn that is in release,
- * and must not start transactions for an unauthenticated subscriber. There will obviously be only one
- * conn for this vsub, so return NULL right away. */
- if (!ran_conn_is_accepted(conn))
- return NULL;
- return conn;
- }
-
- return NULL;
-}
diff --git a/src/libmsc/gsup_client_mux.c b/src/libmsc/gsup_client_mux.c
new file mode 100644
index 000000000..7ec1712e7
--- /dev/null
+++ b/src/libmsc/gsup_client_mux.c
@@ -0,0 +1,163 @@
+/* Directing individual GSUP messages to their respective handlers. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <errno.h>
+
+#include <osmocom/gsupclient/gsup_client.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsup_client_mux.h>
+
+static enum osmo_gsup_message_class gsup_client_mux_classify(struct gsup_client_mux *gcm,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ if (gsup_msg->message_class)
+ return gsup_msg->message_class;
+
+ LOGP(DLGSUP, LOGL_DEBUG, "No explicit GSUP Message Class, trying to guess from message type %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+
+ switch (gsup_msg->message_type) {
+ case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
+ case OSMO_GSUP_MSGT_PROC_SS_RESULT:
+ case OSMO_GSUP_MSGT_PROC_SS_ERROR:
+ return OSMO_GSUP_MESSAGE_CLASS_USSD;
+
+ /* GSM 04.11 code implementing MO SMS */
+ case OSMO_GSUP_MSGT_MO_FORWARD_SM_ERROR:
+ case OSMO_GSUP_MSGT_MO_FORWARD_SM_RESULT:
+ case OSMO_GSUP_MSGT_READY_FOR_SM_ERROR:
+ case OSMO_GSUP_MSGT_READY_FOR_SM_RESULT:
+ case OSMO_GSUP_MSGT_MT_FORWARD_SM_REQUEST:
+ return OSMO_GSUP_MESSAGE_CLASS_SMS;
+
+ default:
+ return OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT;
+ }
+}
+
+/* Non-static for unit tests */
+int gsup_client_mux_rx(struct osmo_gsup_client *gsup_client, struct msgb *msg)
+{
+ struct gsup_client_mux *gcm = gsup_client->data;
+ struct osmo_gsup_message gsup;
+ enum osmo_gsup_message_class message_class;
+ int rc;
+
+ rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
+ if (rc < 0) {
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
+ get_value_string(gsm48_gmm_cause_names, -rc), -rc, osmo_hexdump(msg->data, msg->len));
+ goto msgb_free_and_return;
+ }
+
+ if (!gsup.imsi[0]) {
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to decode GSUP message: missing IMSI\n");
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup.message_type))
+ gsup_client_mux_tx_error_reply(gcm, &gsup, GMM_CAUSE_INV_MAND_INFO);
+ rc = -GMM_CAUSE_INV_MAND_INFO;
+ goto msgb_free_and_return;
+ }
+
+ message_class = gsup_client_mux_classify(gcm, &gsup);
+
+ if (message_class <= OSMO_GSUP_MESSAGE_CLASS_UNSET || message_class >= ARRAY_SIZE(gcm->rx_cb)) {
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to classify GSUP message target\n");
+ rc = -EINVAL;
+ goto msgb_free_and_return;
+ }
+
+ if (!gcm->rx_cb[message_class].func) {
+ LOGP(DLGSUP, LOGL_ERROR, "No receiver set up for GSUP Message Class %s\n", osmo_gsup_message_class_name(message_class));
+ rc = -ENOTSUP;
+ goto msgb_free_and_return;
+ }
+
+ rc = gcm->rx_cb[message_class].func(gcm, gcm->rx_cb[message_class].data, &gsup);
+
+msgb_free_and_return:
+ msgb_free(msg);
+ return rc;
+}
+
+/* Make it clear that struct gsup_client_mux should be talloc allocated, so that it can be used as talloc parent. */
+struct gsup_client_mux *gsup_client_mux_alloc(void *talloc_ctx)
+{
+ return talloc_zero(talloc_ctx, struct gsup_client_mux);
+}
+
+/* Start a GSUP client to serve this gsup_client_mux. */
+int gsup_client_mux_start(struct gsup_client_mux *gcm, const char *gsup_server_addr_str, uint16_t gsup_server_port,
+ struct ipaccess_unit *ipa_dev)
+{
+ gcm->gsup_client = osmo_gsup_client_create2(gcm, ipa_dev,
+ gsup_server_addr_str,
+ gsup_server_port,
+ &gsup_client_mux_rx, NULL);
+ if (!gcm->gsup_client)
+ return -ENOMEM;
+ gcm->gsup_client->data = gcm;
+ return 0;
+}
+
+int gsup_client_mux_tx(struct gsup_client_mux *gcm, const struct osmo_gsup_message *gsup_msg)
+{
+ struct msgb *msg;
+ int rc;
+
+ if (!gcm || !gcm->gsup_client) {
+ LOGP(DLGSUP, LOGL_ERROR, "GSUP link is down, cannot send GSUP message\n");
+ return -ENOTSUP;
+ }
+
+ msg = osmo_gsup_client_msgb_alloc();
+ rc = osmo_gsup_encode(msg, gsup_msg);
+ if (rc < 0) {
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to encode GSUP message: '%s'\n", strerror(-rc));
+ return rc;
+ }
+
+ return osmo_gsup_client_send(gcm->gsup_client, msg);
+}
+
+/* Transmit GSUP error in response to original message */
+void gsup_client_mux_tx_error_reply(struct gsup_client_mux *gcm, const struct osmo_gsup_message *gsup_orig,
+ enum gsm48_gmm_cause cause)
+{
+ struct osmo_gsup_message gsup_reply;
+
+ /* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
+ if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
+ return;
+
+ OSMO_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi);
+
+ gsup_reply = (struct osmo_gsup_message){
+ .cause = cause,
+ .message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
+ };
+
+ if (osmo_gsup_client_enc_send(gcm->gsup_client, &gsup_reply))
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
+ osmo_quote_str(gsup_orig->imsi, -1));
+}
diff --git a/src/libmsc/iu_dummy.c b/src/libmsc/iu_dummy.c
deleted file mode 100644
index 277ec07db..000000000
--- a/src/libmsc/iu_dummy.c
+++ /dev/null
@@ -1,99 +0,0 @@
-/* Trivial switch-off of external Iu dependencies,
- * allowing to run full unit tests even when built without Iu support. */
-
-/*
- * (C) 2016,2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
- *
- * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "../../bscconfig.h"
-#ifndef BUILD_IU
-
-#include <osmocom/msc/iu_dummy.h>
-
-#include <osmocom/core/logging.h>
-#include <osmocom/vty/logging.h>
-#include <osmocom/core/msgb.h>
-
-struct msgb;
-struct ranap_ue_conn_ctx;
-struct RANAP_Cause;
-struct osmo_auth_vector;
-
-int ranap_iu_tx(struct msgb *msg, uint8_t sapi)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_tx() dummy called, NOT transmitting %d bytes: %s\n",
- msg->len, osmo_hexdump(msg->data, msg->len));
- return 0;
-}
-
-int ranap_iu_tx_sec_mode_cmd(struct ranap_ue_conn_ctx *uectx, struct osmo_auth_vector *vec,
- int send_ck)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_sec_mode_cmd() dummy called, NOT transmitting Security Mode Command\n");
- return 0;
-}
-
-int ranap_iu_page_cs(const char *imsi, const uint32_t *tmsi, uint16_t lac)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_page_cs() dummy called, NOT paging\n");
- return 23;
-}
-
-int ranap_iu_page_ps(const char *imsi, const uint32_t *ptmsi, uint16_t lac, uint8_t rac)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_page_ps() dummy called, NOT paging\n");
- return 0;
-}
-
-struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id, uint32_t rtp_ip,
- uint16_t rtp_port,
- bool use_x213_nsap)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "ranap_new_msg_rab_assign_voice() dummy called, NOT composing RAB Assignment\n");
- return NULL;
-}
-
-int ranap_iu_rab_act(struct ranap_ue_conn_ctx *ue_ctx, struct msgb *msg)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_rab_act() dummy called, NOT activating RAB\n");
- return 0;
-}
-
-int ranap_iu_tx_common_id(struct ranap_ue_conn_ctx *uectx, const char *imsi)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_common_id() dummy called, NOT sending CommonID\n");
- return 0;
-}
-
-int ranap_iu_tx_release(struct ranap_ue_conn_ctx *ctx, const struct RANAP_Cause *cause)
-{
- LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_release() dummy called, NOT sending Release\n");
- return 0;
-}
-
-uint32_t iu_get_conn_id(const struct ranap_ue_conn_ctx *ue)
-{
- /* There is a bogus conn_id in the bogus struct ranap_ue_conn_ctx, managed for unit testing of Iu even in the
- * absence of libosmo-ranap (when built without Iu support). */
- return ue->conn_id;
-}
-
-#endif
diff --git a/src/libmsc/iucs.c b/src/libmsc/iucs.c
deleted file mode 100644
index 974ddb3f7..000000000
--- a/src/libmsc/iucs.c
+++ /dev/null
@@ -1,249 +0,0 @@
-/* Code to manage MSC RAN connections over IuCS interface */
-
-/*
- * (C) 2016,2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
- *
- * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include <inttypes.h>
-
-#include <osmocom/core/logging.h>
-#include <osmocom/ranap/iu_client.h>
-#include <osmocom/msc/debug.h>
-
-#include <osmocom/msc/gsm_data.h>
-#include <osmocom/msc/gsm_subscriber.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/core/byteswap.h>
-
-#include "../../bscconfig.h"
-
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-extern struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id,
- uint32_t rtp_ip,
- uint16_t rtp_port,
- bool use_x213_nsap);
-#else
-#include <osmocom/msc/iu_dummy.h>
-#endif /* BUILD_IU */
-
-/* For A-interface see libbsc/bsc_api.c subscr_con_allocate() */
-static struct ran_conn *ran_conn_allocate_iu(struct gsm_network *network,
- struct ranap_ue_conn_ctx *ue,
- uint16_t lac)
-{
- struct ran_conn *conn;
-
- DEBUGP(DIUCS, "Allocating IuCS RAN conn: lac %d, conn_id %" PRIx32 "\n",
- lac, ue->conn_id);
-
- conn = ran_conn_alloc(network, OSMO_RAT_UTRAN_IU, lac);
- if (!conn)
- return NULL;
-
- conn->iu.ue_ctx = ue;
- conn->iu.ue_ctx->rab_assign_addr_enc = network->iu.rab_assign_addr_enc;
- return conn;
-}
-
-static int same_ue_conn(struct ranap_ue_conn_ctx *a, struct ranap_ue_conn_ctx *b)
-{
- if (a == b)
- return 1;
- return (a->conn_id == b->conn_id);
-}
-
-static inline void log_subscribers(struct gsm_network *network)
-{
- if (!log_check_level(DIUCS, LOGL_DEBUG))
- return;
-
- struct ran_conn *conn;
- int i = 0;
- llist_for_each_entry(conn, &network->ran_conns, entry) {
- DEBUGP(DIUCS, "%3d: %s", i, vlr_subscr_name(conn->vsub));
- switch (conn->via_ran) {
- case OSMO_RAT_UTRAN_IU:
- DEBUGPC(DIUCS, " Iu");
- if (conn->iu.ue_ctx) {
- DEBUGPC(DIUCS, " conn_id %d",
- conn->iu.ue_ctx->conn_id
- );
- }
- break;
- case OSMO_RAT_GERAN_A:
- DEBUGPC(DIUCS, " A");
- /* TODO log A-interface connection details */
- break;
- case OSMO_RAT_UNKNOWN:
- DEBUGPC(DIUCS, " ?");
- break;
- default:
- DEBUGPC(DIUCS, " invalid");
- break;
- }
- DEBUGPC(DIUCS, "\n");
- i++;
- }
- DEBUGP(DIUCS, "subscribers registered: %d\n", i);
-}
-
-/* Return an existing IuCS RAN connection record for the given
- * connection IDs, or return NULL if not found. */
-struct ran_conn *ran_conn_lookup_iu(
- struct gsm_network *network,
- struct ranap_ue_conn_ctx *ue)
-{
- struct ran_conn *conn;
-
- DEBUGP(DIUCS, "Looking for IuCS subscriber: conn_id %" PRIx32 "\n",
- ue->conn_id);
- log_subscribers(network);
-
- llist_for_each_entry(conn, &network->ran_conns, entry) {
- if (conn->via_ran != OSMO_RAT_UTRAN_IU)
- continue;
- if (!same_ue_conn(conn->iu.ue_ctx, ue))
- continue;
- DEBUGP(DIUCS, "Found IuCS subscriber for conn_id %" PRIx32 "\n",
- ue->conn_id);
- return conn;
- }
- DEBUGP(DIUCS, "No IuCS subscriber found for conn_id %" PRIx32 "\n",
- ue->conn_id);
- return NULL;
-}
-
-/* Receive MM/CC/... message from IuCS (SCCP user SAP).
- * msg->dst must reference a struct ranap_ue_conn_ctx, which identifies the peer that
- * sent the msg.
- *
- * For A-interface see libbsc/bsc_api.c gsm0408_rcvmsg(). */
-int gsm0408_rcvmsg_iucs(struct gsm_network *network, struct msgb *msg,
- uint16_t *lac)
-{
- struct ranap_ue_conn_ctx *ue_ctx;
- struct ran_conn *conn;
-
- ue_ctx = (struct ranap_ue_conn_ctx*)msg->dst;
-
- /* TODO: are there message types that could allow us to skip this
- * search? */
- conn = ran_conn_lookup_iu(network, ue_ctx);
-
- if (conn && lac && (conn->lac != *lac)) {
- LOGP(DIUCS, LOGL_ERROR, "IuCS subscriber has changed LAC"
- " within the same connection, discarding connection:"
- " %s from LAC %d to %d\n",
- vlr_subscr_name(conn->vsub), conn->lac, *lac);
- /* Deallocate conn with previous LAC */
- ran_conn_close(conn, GSM_CAUSE_INV_MAND_INFO);
- /* At this point we could be tolerant and allocate a new
- * connection, but changing the LAC within the same connection
- * is shifty. Rather cancel everything. */
- return -1;
- }
-
- if (conn) {
- /* Make sure we don't receive RR over IuCS; otherwise all
- * messages handled by gsm0408_dispatch() are of interest (CC,
- * MM, SMS, NS_SS, maybe even MM_GPRS and SM_GPRS). */
- struct gsm48_hdr *gh = msgb_l3(msg);
- uint8_t pdisc = gh->proto_discr & 0x0f;
- OSMO_ASSERT(pdisc != GSM48_PDISC_RR);
-
- ran_conn_dtap(conn, msg);
- } else {
- /* allocate a new connection */
-
- if (!lac) {
- LOGP(DIUCS, LOGL_ERROR, "New IuCS subscriber"
- " but no LAC available. Expecting an InitialUE"
- " message containing a LAI IE."
- " Dropping connection.\n");
- return -1;
- }
-
- conn = ran_conn_allocate_iu(network, ue_ctx, *lac);
- if (!conn)
- abort();
-
- /* ownership of conn hereby goes to the MSC: */
- ran_conn_compl_l3(conn, msg, 0);
- }
-
- return 0;
-}
-
-int iu_rab_act_cs(struct gsm_trans *trans)
-{
- struct ran_conn *conn;
- struct msgb *msg;
- bool use_x213_nsap;
- uint32_t conn_id;
- struct ranap_ue_conn_ctx *uectx;
- uint8_t rab_id;
- uint32_t rtp_ip;
- uint16_t rtp_port;
-
- conn = trans->conn;
- uectx = conn->iu.ue_ctx;
- rab_id = conn->iu.rab_id;
- rtp_ip = osmo_htonl(inet_addr(conn->rtp.local_addr_ran));
- rtp_port = conn->rtp.local_port_ran;
- conn_id = uectx->conn_id;
-
- if (rtp_ip == INADDR_NONE) {
- LOGP(DIUCS, LOGL_DEBUG,
- "Assigning RAB: conn_id=%u, rab_id=%d, invalid RTP IP-Address\n",
- conn_id, rab_id);
- return -EINVAL;
- }
- if (rtp_port == 0) {
- LOGP(DIUCS, LOGL_DEBUG,
- "Assigning RAB: conn_id=%u, rab_id=%d, invalid RTP Port\n",
- conn_id, rab_id);
- return -EINVAL;
- }
-
- use_x213_nsap =
- (uectx->rab_assign_addr_enc == RANAP_NSAP_ADDR_ENC_X213);
-
- LOGP(DIUCS, LOGL_DEBUG,
- "Assigning RAB: conn_id=%u, rab_id=%d, rtp=%x:%u, use_x213_nsap=%d\n",
- conn_id, rab_id, rtp_ip, rtp_port, use_x213_nsap);
-
- msg = ranap_new_msg_rab_assign_voice(rab_id, rtp_ip, rtp_port,
- use_x213_nsap);
- msg->l2h = msg->data;
-
- if (ranap_iu_rab_act(uectx, msg))
- LOGP(DIUCS, LOGL_ERROR,
- "Failed to send RAB Assignment: conn_id=%d rab_id=%d rtp=%x:%u\n",
- conn_id, rab_id, rtp_ip, rtp_port);
- return 0;
-}
-
-uint32_t iu_get_conn_id(const struct ranap_ue_conn_ctx *ue)
-{
- return ue->conn_id;
-}
diff --git a/src/libmsc/iucs_ranap.c b/src/libmsc/iucs_ranap.c
deleted file mode 100644
index 1e4207aec..000000000
--- a/src/libmsc/iucs_ranap.c
+++ /dev/null
@@ -1,137 +0,0 @@
-/* Implementation of RANAP messages to/from an MSC via an Iu-CS interface.
- * This keeps direct RANAP dependencies out of libmsc. */
-
-/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "../../bscconfig.h"
-
-#ifdef BUILD_IU
-
-#include <osmocom/core/logging.h>
-
-#include <osmocom/ranap/ranap_ies_defs.h>
-#include <osmocom/ranap/iu_client.h>
-#include <osmocom/ranap/RANAP_IuTransportAssociation.h>
-#include <osmocom/ranap/iu_helpers.h>
-
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/gsm_data.h>
-#include <osmocom/msc/gsm_subscriber.h>
-#include <osmocom/msc/iucs.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/iucs_ranap.h>
-#include <osmocom/msc/msc_mgcp.h>
-
-#include <asn1c/asn1helpers.h>
-
-/* To continue authorization after a Security Mode Complete */
-int gsm0408_authorize(struct ran_conn *conn);
-
-static int iucs_rx_rab_assign(struct ran_conn *conn, RANAP_RAB_SetupOrModifiedItemIEs_t * setup_ies)
-{
- uint8_t rab_id;
- RANAP_RAB_SetupOrModifiedItem_t *item = &setup_ies->raB_SetupOrModifiedItem;
- RANAP_TransportLayerAddress_t *transp_layer_addr;
- RANAP_IuTransportAssociation_t *transp_assoc;
- uint16_t port = 0;
- int rc;
- char addr[INET_ADDRSTRLEN];
-
- rab_id = item->rAB_ID.buf[0];
-
- LOGP(DIUCS, LOGL_NOTICE,
- "Received RAB assignment event for %s rab_id=%hhd\n", vlr_subscr_name(conn->vsub), rab_id);
-
- if (item->iuTransportAssociation && item->transportLayerAddress) {
- transp_layer_addr = item->transportLayerAddress;
- transp_assoc = item->iuTransportAssociation;
-
- rc = ranap_transp_assoc_decode(&port, transp_assoc);
- if (rc != 0) {
- LOGP(DIUCS, LOGL_ERROR,
- "Unable to decode RTP port in RAB assignment (%s rab_id=%hhd)\n",
- vlr_subscr_name(conn->vsub), rab_id);
- return 0;
- }
-
- rc = ranap_transp_layer_addr_decode(addr, sizeof(addr), transp_layer_addr);
- if (rc != 0) {
- LOGP(DIUCS, LOGL_ERROR,
- "Unable to decode IP-Address in RAB assignment (%s rab_id=%hhd)\n",
- vlr_subscr_name(conn->vsub), rab_id);
- return 0;
- }
-
- return msc_mgcp_ass_complete(conn, port, addr);
- }
-
- LOGP(DIUCS, LOGL_ERROR,
- "RAB assignment lacks RTP connection information. (%s rab_id=%hhd)\n",
- vlr_subscr_name(conn->vsub), rab_id);
- return 0;
-}
-
-int iucs_rx_sec_mode_compl(struct ran_conn *conn,
- RANAP_SecurityModeCompleteIEs_t *ies)
-{
- OSMO_ASSERT(conn->via_ran == OSMO_RAT_UTRAN_IU);
-
- /* TODO evalute ies */
-
- ran_conn_rx_sec_mode_compl(conn);
- return 0;
-}
-
-int iucs_rx_ranap_event(struct gsm_network *network,
- struct ranap_ue_conn_ctx *ue_ctx, int type, void *data)
-{
- struct ran_conn *conn;
-
- conn = ran_conn_lookup_iu(network, ue_ctx);
-
- if (!conn) {
- LOGP(DRANAP, LOGL_ERROR, "Cannot find subscriber for IU event %u\n", type);
- return -1;
- }
-
- switch (type) {
- case RANAP_IU_EVENT_IU_RELEASE:
- case RANAP_IU_EVENT_LINK_INVALIDATED:
- LOGP(DIUCS, LOGL_INFO, "IuCS release for %s\n",
- vlr_subscr_name(conn->vsub));
- ran_conn_rx_iu_release_complete(conn);
- return 0;
-
- case RANAP_IU_EVENT_SECURITY_MODE_COMPLETE:
- LOGP(DIUCS, LOGL_INFO, "IuCS security mode complete for %s\n",
- vlr_subscr_name(conn->vsub));
- return iucs_rx_sec_mode_compl(conn,
- (RANAP_SecurityModeCompleteIEs_t*)data);
- case RANAP_IU_EVENT_RAB_ASSIGN:
- return iucs_rx_rab_assign(conn,
- (RANAP_RAB_SetupOrModifiedItemIEs_t*)data);
- default:
- LOGP(DIUCS, LOGL_NOTICE, "Unknown message received:"
- " RANAP event: %i\n", type);
- return -1;
- }
-}
-
-#endif /* BUILD_IU */
diff --git a/src/libmsc/mncc.c b/src/libmsc/mncc.c
index d2cd170a8..8c95ecb14 100644
--- a/src/libmsc/mncc.c
+++ b/src/libmsc/mncc.c
@@ -286,3 +286,103 @@ int mncc_prim_check(const struct gsm_mncc *mncc_prim, unsigned int len)
}
return 0;
}
+
+static uint8_t mncc_speech_ver_to_perm_speech(int speech_ver)
+{
+ /* The speech versions that are transmitted in the Bearer capability
+ * information element, that is transmitted on the Layer 3 (CC)
+ * 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;
+ case GSM48_BCAP_SV_HR:
+ return GSM0808_PERM_HR1;
+ case GSM48_BCAP_SV_EFR:
+ return GSM0808_PERM_FR2;
+ case GSM48_BCAP_SV_AMR_F:
+ return GSM0808_PERM_FR3;
+ case GSM48_BCAP_SV_AMR_H:
+ return GSM0808_PERM_HR3;
+ case GSM48_BCAP_SV_AMR_OFW:
+ return GSM0808_PERM_FR4;
+ case GSM48_BCAP_SV_AMR_OHW:
+ return GSM0808_PERM_HR4;
+ case GSM48_BCAP_SV_AMR_FW:
+ return GSM0808_PERM_FR5;
+ case GSM48_BCAP_SV_AMR_OH:
+ return GSM0808_PERM_HR6;
+ }
+
+ /* If nothing matches, tag the result as invalid */
+ LOGP(DBSSAP, LOGL_ERROR, "Invalid permitted speech version: %d\n", speech_ver);
+ return 0xFF;
+}
+
+/* Convert speech preference field */
+static uint8_t mncc_bc_radio_to_speech_pref(int radio)
+{
+ /* The Radio channel requirement field that is transmitted in the
+ * Bearer capability information element, that is transmitted on the
+ * Layer 3 (CC) 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(DBSSAP, LOGL_ERROR, "Invalid radio channel preference: %d; defaulting to full rate.\n", radio);
+ return GSM0808_SPEECH_FULL_BM;
+}
+
+int mncc_bearer_cap_to_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;
+
+ ct->ch_indctr = GSM0808_CHAN_SPEECH;
+
+ for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) {
+ if (bc->speech_ver[i] == -1)
+ break;
+ sv = mncc_speech_ver_to_perm_speech(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 = mncc_bc_radio_to_speech_pref(bc->radio);
+
+ if (count)
+ return 0;
+ else
+ return -EINVAL;
+}
diff --git a/src/libmsc/mncc_builtin.c b/src/libmsc/mncc_builtin.c
index fbdc5b4b5..6dd7e350a 100644
--- a/src/libmsc/mncc_builtin.c
+++ b/src/libmsc/mncc_builtin.c
@@ -79,7 +79,7 @@ static int mncc_setup_ind(struct gsm_call *call, int msg_type,
/* already have remote call */
if (call->remote_ref)
return 0;
-
+
/* transfer mode 1 would be packet mode, which was never specified */
if (setup->bearer_cap.mode != 0) {
LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support "
@@ -254,7 +254,7 @@ int int_mncc_recv(struct gsm_network *net, struct msgb *msg)
/* Special messages */
switch(msg_type) {
}
-
+
/* find callref */
callref = data->callref;
llist_for_each_entry(callt, &call_list, entry) {
@@ -271,7 +271,7 @@ int int_mncc_recv(struct gsm_network *net, struct msgb *msg)
/* create call */
if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
struct gsm_mncc rel;
-
+
memset(&rel, 0, sizeof(struct gsm_mncc));
rel.callref = callref;
mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
diff --git a/src/libmsc/mncc_call.c b/src/libmsc/mncc_call.c
new file mode 100644
index 000000000..6ea402453
--- /dev/null
+++ b/src/libmsc/mncc_call.c
@@ -0,0 +1,760 @@
+/* Handle an MNCC managed call (external MNCC). */
+/* At the time of writing, this is only used for inter-MSC handover: forward a voice stream to a remote MSC.
+ * Maybe it makes sense to also use it for all "normal" external call management at some point. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <string.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/msc/mncc_call.h>
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/vlr.h>
+
+struct osmo_fsm mncc_call_fsm;
+static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call);
+
+LLIST_HEAD(mncc_call_list);
+
+static const struct osmo_tdef_state_timeout mncc_call_fsm_timeouts[32] = {
+ /* TODO */
+};
+
+struct gsm_network *gsmnet = NULL;
+
+/* Transition to a state, using the T timer defined in msc_a_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define mncc_call_fsm_state_chg(MNCC, STATE) \
+ osmo_tdef_fsm_inst_state_chg((MNCC)->fi, STATE, mncc_call_fsm_timeouts, gsmnet->mncc_tdefs, 5)
+
+#define mncc_call_error(MNCC, FMT, ARGS...) do { \
+ LOG_MNCC_CALL(MNCC, LOGL_ERROR, FMT, ##ARGS); \
+ osmo_fsm_inst_term((MNCC)->fi, OSMO_FSM_TERM_REGULAR, 0); \
+ } while(0)
+
+void mncc_call_fsm_init(struct gsm_network *net)
+{
+ osmo_fsm_register(&mncc_call_fsm);
+ gsmnet = net;
+}
+
+void mncc_call_fsm_update_id(struct mncc_call *mncc_call)
+{
+ osmo_fsm_inst_update_id_f_sanitize(mncc_call->fi, '-', "%s:callref-0x%x%s%s",
+ vlr_subscr_name(mncc_call->vsub), mncc_call->callref,
+ mncc_call->remote_msisdn_present ? ":to-msisdn-" : "",
+ mncc_call->remote_msisdn_present ? mncc_call->remote_msisdn.number : "");
+}
+
+/* Invoked by the socket read callback in case the given MNCC call instance is responsible for the given callref. */
+void mncc_call_rx(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
+{
+ if (!mncc_call)
+ return;
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Rx %s\n", get_mncc_name(mncc_msg->msg_type));
+ osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_RX_MNCC_MSG, (void*)mncc_msg);
+}
+
+/* Send an MNCC message (associated with this MNCC call). */
+int mncc_call_tx(struct mncc_call *mncc_call, union mncc_msg *mncc_msg)
+{
+ struct msgb *msg;
+ unsigned char *data;
+
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "tx %s\n", get_mncc_name(mncc_msg->msg_type));
+
+ msg = msgb_alloc(sizeof(*mncc_msg), "MNCC-tx");
+ OSMO_ASSERT(msg);
+
+ data = msgb_put(msg, sizeof(*mncc_msg));
+ memcpy(data, mncc_msg, sizeof(*mncc_msg));
+
+ if (gsmnet->mncc_recv(gsmnet, msg)) {
+ mncc_call_error(mncc_call, "Failed to send MNCC message %s\n", get_mncc_name(mncc_msg->msg_type));
+ return -EIO;
+ }
+ return 0;
+}
+
+/* Send a trivial MNCC message with just a message type (associated with this MNCC call). */
+int mncc_call_tx_msgt(struct mncc_call *mncc_call, uint32_t msg_type)
+{
+ union mncc_msg mncc_msg = {
+ .signal = {
+ .msg_type = msg_type,
+ .callref = mncc_call->callref,
+ },
+ };
+ return mncc_call_tx(mncc_call, &mncc_msg);
+}
+
+/* Allocate an MNCC FSM as child of the given MSC role FSM.
+ * parent_event_call_released is mandatory and is passed as the parent_term_event.
+ * parent_event_call_setup_complete is dispatched when the MNCC FSM enters the MNCC_CALL_ST_TALKING state.
+ * parent_event_call_setup_complete is optional, pass a negative number to avoid dispatching.
+ *
+ * If non-NULL, message_cb is invoked whenever an MNCC message is received from the the MNCC socket, which is useful to
+ * forward things like DTMF to CC or to another MNCC call.
+ *
+ * After mncc_call_alloc(), call either mncc_call_outgoing_start() or mncc_call_incoming_start().
+ */
+struct mncc_call *mncc_call_alloc(struct vlr_subscr *vsub,
+ struct osmo_fsm_inst *parent,
+ int parent_event_call_setup_complete,
+ uint32_t parent_event_call_released,
+ mncc_call_message_cb_t message_cb, void *forward_cb_data)
+{
+ struct mncc_call *mncc_call;
+ struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&mncc_call_fsm, parent, parent_event_call_released);
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(vsub);
+
+ mncc_call = talloc(fi, struct mncc_call);
+ OSMO_ASSERT(mncc_call);
+ fi->priv = mncc_call;
+
+ *mncc_call = (struct mncc_call){
+ .fi = fi,
+ .vsub = vsub,
+ .parent_event_call_setup_complete = parent_event_call_setup_complete,
+ .message_cb = message_cb,
+ .forward_cb_data = forward_cb_data,
+ };
+
+ llist_add(&mncc_call->entry, &mncc_call_list);
+ mncc_call_fsm_update_id(mncc_call);
+
+ return mncc_call;
+}
+
+void mncc_call_reparent(struct mncc_call *mncc_call,
+ struct osmo_fsm_inst *new_parent,
+ int parent_event_call_setup_complete,
+ uint32_t parent_event_call_released,
+ mncc_call_message_cb_t message_cb, void *forward_cb_data)
+{
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n",
+ mncc_call->fi->proc.parent->name, new_parent->name);
+ osmo_fsm_inst_change_parent(mncc_call->fi, new_parent, parent_event_call_released);
+ talloc_steal(new_parent, mncc_call->fi);
+ mncc_call->parent_event_call_setup_complete = parent_event_call_setup_complete;
+ mncc_call->message_cb = message_cb;
+ mncc_call->forward_cb_data = forward_cb_data;
+}
+
+/* Associate an rtp_stream with this MNCC call instance (optional).
+ * Can be called directly after mncc_call_alloc(). If an rtp_stream is set, upon receiving the MNCC_RTP_CONNECT containing
+ * the PBX's RTP IP and port, pass the IP:port information to rtp_stream_set_remote_addr() and rtp_stream_commit() to
+ * update the MGW connection. If no rtp_stream is associated, the caller is responsible to manually extract the RTP
+ * IP:port from the MNCC_RTP_CONNECT message forwarded to mncc_call_message_cb_t (see mncc_call_alloc()).
+ * When an rtp_stream is set, call rtp_stream_release() when the MNCC call ends; call mncc_call_detach_rtp_stream() before
+ * the MNCC call releases if that is not desired.
+ */
+int mncc_call_set_rtp_stream(struct mncc_call *mncc_call, struct rtp_stream *rtps)
+{
+ if (mncc_call->rtps && mncc_call->rtps != rtps) {
+ LOG_MNCC_CALL(mncc_call, LOGL_ERROR,
+ "Cannot associate with RTP stream %s, already associated with %s\n",
+ rtps ? rtps->fi->name : "NULL", mncc_call->rtps->fi->name);
+ return -ENOSPC;
+ }
+
+ mncc_call->rtps = rtps;
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Associated with RTP stream %s\n", mncc_call->rtps->fi->name);
+ return 0;
+}
+
+/* Disassociate the rtp_stream from this MNCC call instance, and clear the remote RTP IP:port info.
+ * When the MNCC FSM ends for any reason, it will release the RTP stream (which usually triggers complete tear down of
+ * the call_leg and CC transaction). If the RTP stream should still remain in use, e.g. during Subseqent inter-MSC
+ * Handover where this MNCC was a forwarding to a remote MSC that is no longer needed, this function must be called
+ * before the MNCC FSM instance terminates. Call this *before* setting a new remote RTP address on the rtp_stream, since
+ * this clears the rtp_stream->remote ip:port information. */
+void mncc_call_detach_rtp_stream(struct mncc_call *mncc_call)
+{
+ struct rtp_stream *rtps = mncc_call->rtps;
+ struct osmo_sockaddr_str clear;
+ if (!rtps)
+ return;
+ mncc_call->rtps = NULL;
+ rtp_stream_set_remote_addr(rtps, &clear);
+}
+
+static void mncc_call_tx_setup_ind(struct mncc_call *mncc_call)
+{
+ struct gsm_mncc mncc_msg = mncc_call->outgoing_req;
+ mncc_msg.msg_type = MNCC_SETUP_IND;
+ mncc_msg.callref = mncc_call->callref;
+
+ OSMO_STRLCPY_ARRAY(mncc_msg.imsi, mncc_call->vsub->imsi);
+
+ if (!(mncc_call->outgoing_req.fields & MNCC_F_CALLING)) {
+ /* No explicit calling number set, use the local subscriber */
+ mncc_msg.fields |= MNCC_F_CALLING;
+ OSMO_STRLCPY_ARRAY(mncc_msg.calling.number, mncc_call->vsub->msisdn);
+
+ }
+ mncc_call->local_msisdn_present = true;
+ mncc_call->local_msisdn = mncc_msg.calling;
+
+ rate_ctr_inc(&gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]);
+
+ mncc_call_tx(mncc_call, (union mncc_msg*)&mncc_msg);
+}
+
+static void mncc_call_rx_setup_req(struct mncc_call *mncc_call, const struct gsm_mncc *incoming_req)
+{
+ mncc_call->callref = incoming_req->callref;
+
+ if (incoming_req->fields & MNCC_F_CALLED) {
+ mncc_call->local_msisdn_present = true;
+ mncc_call->local_msisdn = incoming_req->called;
+ }
+
+ if (incoming_req->fields & MNCC_F_CALLING) {
+ mncc_call->remote_msisdn_present = true;
+ mncc_call->remote_msisdn = incoming_req->calling;
+ }
+
+ mncc_call_fsm_update_id(mncc_call);
+}
+
+/* Remote PBX asks for RTP_CREATE. This merely asks us to create an RTP stream, and does not actually contain any useful
+ * information like the remote RTP IP:port (these follow in the RTP_CONNECT from the SIP side) */
+static bool mncc_call_rx_rtp_create(struct mncc_call *mncc_call)
+{
+ mncc_call->received_rtp_create = true;
+
+ if (!mncc_call->rtps) {
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but no RTP stream associated\n");
+ return true;
+ }
+
+ if (!osmo_sockaddr_str_is_set(&mncc_call->rtps->local)) {
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no local address\n");
+ return true;
+ }
+
+ if (!mncc_call->rtps->codec_known) {
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no codec set\n");
+ return true;
+ }
+
+ LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, responding with " OSMO_SOCKADDR_STR_FMT " %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&mncc_call->rtps->local),
+ osmo_mgcpc_codec_name(mncc_call->rtps->codec));
+ /* Already know what RTP IP:port to tell the MNCC. Send it. */
+ return mncc_call_tx_rtp_create(mncc_call);
+}
+
+/* Convert enum mgcp_codecs to an gsm_mncc_rtp->payload_msg_type value. */
+uint32_t mgcp_codec_to_mncc_payload_msg_type(enum mgcp_codecs codec)
+{
+ switch (codec) {
+ default:
+ /* disclaimer: i have no idea what i'm doing. */
+ case CODEC_GSM_8000_1:
+ return GSM_TCHF_FRAME;
+ case CODEC_GSMEFR_8000_1:
+ return GSM_TCHF_FRAME_EFR;
+ case CODEC_GSMHR_8000_1:
+ return GSM_TCHH_FRAME;
+ case CODEC_AMR_8000_1:
+ case CODEC_AMRWB_16000_1:
+ //return GSM_TCHF_FRAME;
+ return GSM_TCH_FRAME_AMR;
+ }
+}
+
+static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call)
+{
+ if (!mncc_call->rtps || !osmo_sockaddr_str_is_set(&mncc_call->rtps->local)) {
+ mncc_call_error(mncc_call, "Cannot send RTP_CREATE, no local RTP address set up\n");
+ return false;
+ }
+ struct osmo_sockaddr_str *rtp_local = &mncc_call->rtps->local;
+ union mncc_msg mncc_msg = {
+ .rtp = {
+ .msg_type = MNCC_RTP_CREATE,
+ .callref = mncc_call->callref,
+ .port = rtp_local->port,
+ },
+ };
+
+ if (osmo_sockaddr_str_to_32n(rtp_local, &mncc_msg.rtp.ip)) {
+ mncc_call_error(mncc_call, "Failed to compose IP address " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(rtp_local));
+ return false;
+ }
+
+ if (mncc_call->rtps->codec_known) {
+ mncc_msg.rtp.payload_type = 0; /* ??? */
+ mncc_msg.rtp.payload_msg_type = mgcp_codec_to_mncc_payload_msg_type(mncc_call->rtps->codec);
+ }
+
+ if (mncc_call_tx(mncc_call, &mncc_msg))
+ return false;
+ return true;
+}
+
+static bool mncc_call_rx_rtp_connect(struct mncc_call *mncc_call, const struct gsm_mncc_rtp *mncc_msg)
+{
+ struct osmo_sockaddr_str rtp;
+
+ if (!mncc_call->rtps) {
+ /* The user has not associated an RTP stream, hence we're not supposed to take any action here. */
+ return true;
+ }
+
+ if (osmo_sockaddr_str_from_32n(&rtp, mncc_msg->ip, mncc_msg->port)) {
+ mncc_call_error(mncc_call, "Cannot RTP-CONNECT, invalid RTP IP:port in incoming MNCC message\n");
+ return false;
+ }
+
+ rtp_stream_set_remote_addr(mncc_call->rtps, &rtp);
+ if (rtp_stream_commit(mncc_call->rtps)) {
+ mncc_call_error(mncc_call, "RTP-CONNECT, failed, RTP stream is not properly set up: %s\n",
+ mncc_call->rtps->fi->id);
+ return false;
+ }
+ return true;
+}
+
+/* Return true if the FSM instance still exists after this call, false if it was terminated. */
+static bool mncc_call_rx_release_msg(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
+{
+ switch (mncc_msg->msg_type) {
+ case MNCC_DISC_REQ:
+ /* Remote call leg ended the call, MNCC tells us to DISC. We ack with a REL. */
+ mncc_call_tx_msgt(mncc_call, MNCC_REL_IND);
+ osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, 0);
+ return false;
+
+ case MNCC_REL_REQ:
+ /* MNCC acks with a REL to a previous DISC IND we have (probably) sent.
+ * We ack with a REL CNF. */
+ mncc_call_tx_msgt(mncc_call, MNCC_REL_CNF);
+ osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, 0);
+ return false;
+
+ default:
+ return true;
+ }
+}
+
+/* Return true if the FSM instance still exists after this call, false if it was terminated. */
+static bool mncc_call_rx_common_msg(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
+{
+ switch (mncc_msg->msg_type) {
+ case MNCC_RTP_CREATE:
+ mncc_call_rx_rtp_create(mncc_call);
+ return true;
+
+ case MNCC_RTP_CONNECT:
+ mncc_call_rx_rtp_connect(mncc_call, &mncc_msg->rtp);
+ return true;
+
+ default:
+ return mncc_call_rx_release_msg(mncc_call, mncc_msg);
+ }
+}
+
+static void mncc_call_forward(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
+{
+ if (!mncc_call || !mncc_call->message_cb)
+ return;
+ mncc_call->message_cb(mncc_call, mncc_msg, mncc_call->forward_cb_data);
+}
+
+/* Initiate an outgoing call.
+ * The outgoing_req represents the details for the MNCC_SETUP_IND message sent to initiate the outgoing call. Pass at
+ * least a called number (set outgoing_req->fields |= MNCC_F_CALLED and populate outgoing_req->called). All other items
+ * are optional and can be included if required. The message type, callref and IMSI from this struct are ignored,
+ * instead they are determined internally upon sending the MNCC message. If no calling number is set in the message
+ * struct, it will be set from mncc_call->vsub->msisdn.
+ */
+int mncc_call_outgoing_start(struct mncc_call *mncc_call, const struct gsm_mncc *outgoing_req)
+{
+ if (!mncc_call)
+ return -EINVAL;
+ /* By dispatching an event instead of taking direct action, make sure that the FSM permits starting an outgoing
+ * call. */
+ return osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_OUTGOING_START, (void*)outgoing_req);
+}
+
+/* Handle an incoming call.
+ * When the MNCC recv callback (not included in this mncc_call_fsm API) detects an incoming call (MNCC_SETUP_REQ), take over
+ * handling of the incoming call by the given mncc_call instance.
+ * In incoming_req->setup_req_msg, pass the struct gsm_mncc message containing the received MNCC_SETUP_REQ.
+ * mncc_call_incoming_start() will immediately respond with a MNCC_CALL_CONF_IND; in incoming_req->bearer_cap, pass the
+ * bearer capabilities that should be included in this MNCC_CALL_CONF_IND message; in incoming_req->cccap, pass the
+ * CCCAP to be sent, if any.
+ */
+int mncc_call_incoming_start(struct mncc_call *mncc_call, const struct mncc_call_incoming_req *incoming_req)
+{
+ if (!mncc_call)
+ return -EINVAL;
+ /* By dispatching an event instead of taking direct action, make sure that the FSM permits starting an incoming
+ * call. */
+ return osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_INCOMING_START, (void*)incoming_req);
+}
+
+static void mncc_call_incoming_tx_call_conf_ind(struct mncc_call *mncc_call, const struct gsm_mncc_bearer_cap *bearer_cap)
+{
+ if (mncc_call->fi->state != MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) {
+ LOG_MNCC_CALL(mncc_call, LOGL_ERROR, "%s not allowed in this state\n", __func__);
+ return;
+ }
+
+ union mncc_msg mncc_msg = {
+ .signal = {
+ .msg_type = MNCC_CALL_CONF_IND,
+ .callref = mncc_call->callref,
+ },
+ };
+
+ if (bearer_cap) {
+ mncc_msg.signal.fields |= MNCC_F_BEARER_CAP;
+ mncc_msg.signal.bearer_cap = *bearer_cap;
+ }
+
+ mncc_call_tx(mncc_call, &mncc_msg);
+}
+
+/* Send an MNCC_SETUP_CNF message. Typically after the local side is ready to receive the call and RTP (e.g. for a GSM
+ * CC call, the lchan and RTP should be ready and the CC call should have been confirmed and alerting).
+ * For inter-MSC call forwarding, this can happen immediately upon the MNCC_RTP_CREATE.
+ */
+int mncc_call_incoming_tx_setup_cnf(struct mncc_call *mncc_call, const struct gsm_mncc_number *connected_number)
+{
+ if (mncc_call->fi->state != MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) {
+ LOG_MNCC_CALL(mncc_call, LOGL_ERROR, "%s not allowed in this state\n", __func__);
+ return -EINVAL;
+ }
+
+ union mncc_msg mncc_msg = {
+ .signal = {
+ .msg_type = MNCC_SETUP_CNF,
+ .callref = mncc_call->callref,
+ },
+ };
+
+ if (connected_number) {
+ mncc_msg.signal.fields |= MNCC_F_CONNECTED;
+ mncc_msg.signal.connected = *connected_number;
+ }
+
+ return mncc_call_tx(mncc_call, &mncc_msg);
+}
+
+static void mncc_call_fsm_not_started(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mncc_call *mncc_call = fi->priv;
+ const struct gsm_mncc *outgoing_req;
+ const struct mncc_call_incoming_req *incoming_req;
+
+ switch (event) {
+ case MNCC_CALL_EV_OUTGOING_START:
+ outgoing_req = data;
+ mncc_call->outgoing_req = *outgoing_req;
+ mncc_call->callref = msc_cc_next_outgoing_callref();
+ mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING);
+ mncc_call_tx_setup_ind(mncc_call);
+ return;
+
+ case MNCC_CALL_EV_INCOMING_START:
+ incoming_req = data;
+ mncc_call_rx_setup_req(mncc_call, &incoming_req->setup_req_msg);
+ mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_INCOMING_WAIT_COMPLETE);
+ mncc_call_incoming_tx_call_conf_ind(mncc_call, incoming_req->bearer_cap_present ? &incoming_req->bearer_cap : NULL);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void mncc_call_fsm_outgoing_wait_proceeding(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mncc_call *mncc_call = fi->priv;
+ const union mncc_msg *mncc_msg;
+
+ switch (event) {
+ case MNCC_CALL_EV_RX_MNCC_MSG:
+ mncc_msg = data;
+ if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
+ return;
+
+ switch (mncc_msg->msg_type) {
+ case MNCC_CALL_PROC_REQ:
+ mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE);
+ break;
+ default:
+ break;
+ }
+
+ mncc_call_forward(mncc_call, mncc_msg);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ };
+}
+
+static void mncc_call_fsm_outgoing_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mncc_call *mncc_call = fi->priv;
+ const union mncc_msg *mncc_msg;
+
+ switch (event) {
+ case MNCC_CALL_EV_RX_MNCC_MSG:
+ mncc_msg = data;
+ if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
+ return;
+
+ switch (mncc_msg->msg_type) {
+ case MNCC_SETUP_RSP:
+ mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_TALKING);
+ mncc_call_tx_msgt(mncc_call, MNCC_SETUP_COMPL_IND);
+ break;
+ default:
+ break;
+ }
+
+ mncc_call_forward(mncc_call, mncc_msg);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ };
+}
+
+static void mncc_call_fsm_incoming_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mncc_call *mncc_call = fi->priv;
+ const union mncc_msg *mncc_msg;
+
+ switch (event) {
+ case MNCC_CALL_EV_RX_MNCC_MSG:
+ mncc_msg = data;
+ if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
+ return;
+
+ switch (mncc_msg->msg_type) {
+ case MNCC_SETUP_COMPL_REQ:
+ mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_TALKING);
+ break;
+ default:
+ break;
+ }
+
+ mncc_call_forward(mncc_call, mncc_msg);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ };
+}
+
+static void mncc_call_fsm_talking(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mncc_call *mncc_call = fi->priv;
+ const union mncc_msg *mncc_msg;
+
+ switch (event) {
+ case MNCC_CALL_EV_RX_MNCC_MSG:
+ mncc_msg = data;
+ if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
+ return;
+ mncc_call_forward(mncc_call, mncc_msg);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ };
+}
+
+static void mncc_call_fsm_wait_release_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mncc_call *mncc_call = fi->priv;
+ const union mncc_msg *mncc_msg;
+
+ switch (event) {
+ case MNCC_CALL_EV_RX_MNCC_MSG:
+ mncc_msg = data;
+ if (!mncc_call_rx_release_msg(mncc_call, mncc_msg))
+ return;
+ mncc_call_forward(mncc_call, mncc_msg);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ };
+}
+
+static void mncc_call_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct mncc_call *mncc_call = fi->priv;
+
+ switch (fi->state) {
+ case MNCC_CALL_ST_NOT_STARTED:
+ case MNCC_CALL_ST_WAIT_RELEASE_ACK:
+ break;
+ default:
+ /* Make sure we did indicate some sort of release */
+ mncc_call_tx_msgt(mncc_call, MNCC_REL_IND);
+ break;
+ }
+
+ /* Releasing the RTP stream should trigger completely tearing down the call leg as well as the CC transaction.
+ * In case of an inter-MSC handover where this MNCC connection is replaced by another MNCC / another BSC
+ * connection, the caller needs to detach the RTP stream from this FSM before terminating it. */
+ if (mncc_call->rtps) {
+ rtp_stream_release(mncc_call->rtps);
+ mncc_call->rtps = NULL;
+ }
+
+ llist_del(&mncc_call->entry);
+}
+
+static int mncc_call_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ return 1;
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state mncc_call_fsm_states[] = {
+ [MNCC_CALL_ST_NOT_STARTED] = {
+ .name = "NOT_STARTED",
+ .in_event_mask = 0
+ | S(MNCC_CALL_EV_OUTGOING_START)
+ | S(MNCC_CALL_EV_INCOMING_START)
+ ,
+ .out_state_mask = 0
+ | S(MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING)
+ | S(MNCC_CALL_ST_INCOMING_WAIT_COMPLETE)
+ ,
+ .action = mncc_call_fsm_not_started,
+ },
+ [MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING] = {
+ .name = "OUTGOING_WAIT_PROCEEDING",
+ .in_event_mask = 0
+ | S(MNCC_CALL_EV_RX_MNCC_MSG)
+ ,
+ .out_state_mask = 0
+ | S(MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE)
+ | S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
+ ,
+ .action = mncc_call_fsm_outgoing_wait_proceeding,
+ },
+ [MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE] = {
+ .name = "OUTGOING_WAIT_COMPLETE",
+ .in_event_mask = 0
+ | S(MNCC_CALL_EV_RX_MNCC_MSG)
+ ,
+ .out_state_mask = 0
+ | S(MNCC_CALL_ST_TALKING)
+ | S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
+ ,
+ .action = mncc_call_fsm_outgoing_wait_complete,
+ },
+ [MNCC_CALL_ST_INCOMING_WAIT_COMPLETE] = {
+ .name = "INCOMING_WAIT_COMPLETE",
+ .in_event_mask = 0
+ | S(MNCC_CALL_EV_RX_MNCC_MSG)
+ ,
+ .out_state_mask = 0
+ | S(MNCC_CALL_ST_TALKING)
+ | S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
+ ,
+ .action = mncc_call_fsm_incoming_wait_complete,
+ },
+ [MNCC_CALL_ST_TALKING] = {
+ .name = "TALKING",
+ .in_event_mask = 0
+ | S(MNCC_CALL_EV_RX_MNCC_MSG)
+ ,
+ .out_state_mask = 0
+ | S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
+ ,
+ .action = mncc_call_fsm_talking,
+ },
+ [MNCC_CALL_ST_WAIT_RELEASE_ACK] = {
+ .name = "WAIT_RELEASE_ACK",
+ .in_event_mask = 0
+ | S(MNCC_CALL_EV_RX_MNCC_MSG)
+ ,
+ .action = mncc_call_fsm_wait_release_ack,
+ },
+};
+
+static const struct value_string mncc_call_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MNCC_CALL_EV_RX_MNCC_MSG),
+
+ OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_START),
+ OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_ALERTING),
+ OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_SETUP_COMPLETE),
+
+ OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_START),
+ OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_SETUP),
+ OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_SETUP_COMPLETE),
+
+ OSMO_VALUE_STRING(MNCC_CALL_EV_CN_RELEASE),
+ OSMO_VALUE_STRING(MNCC_CALL_EV_MS_RELEASE),
+ {}
+};
+
+struct osmo_fsm mncc_call_fsm = {
+ .name = "mncc_call",
+ .states = mncc_call_fsm_states,
+ .num_states = ARRAY_SIZE(mncc_call_fsm_states),
+ .log_subsys = DMNCC,
+ .event_names = mncc_call_fsm_event_names,
+ .timer_cb = mncc_call_fsm_timer_cb,
+ .cleanup = mncc_call_fsm_cleanup,
+};
+
+struct mncc_call *mncc_call_find_by_callref(uint32_t callref)
+{
+ struct mncc_call *mncc_call;
+ llist_for_each_entry(mncc_call, &mncc_call_list, entry) {
+ if (mncc_call->callref == callref)
+ return mncc_call;
+ }
+ return NULL;
+}
+
+void mncc_call_release(struct mncc_call *mncc_call)
+{
+ if (!mncc_call)
+ return;
+ mncc_call_tx_msgt(mncc_call, MNCC_DISC_IND);
+ mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_WAIT_RELEASE_ACK);
+}
diff --git a/src/libmsc/mncc_sock.c b/src/libmsc/mncc_sock.c
index 57b4bd883..b2739e857 100644
--- a/src/libmsc/mncc_sock.c
+++ b/src/libmsc/mncc_sock.c
@@ -56,15 +56,6 @@ int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg)
if (net->mncc_state->conn_bfd.fd < 0) {
LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app "
"but socket is gone\n", get_mncc_name(msg_type));
- if (!mncc_is_data_frame(msg_type)) {
- /* release the request */
- struct gsm_mncc mncc_out;
- memset(&mncc_out, 0, sizeof(mncc_out));
- mncc_out.callref = mncc_in->callref;
- mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU,
- GSM48_CC_CAUSE_TEMP_FAILURE);
- mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out);
- }
/* free the original message */
msgb_free(msg);
return -1;
@@ -92,7 +83,7 @@ static void mncc_sock_close(struct mncc_sock_state *state)
state->listen_bfd.when |= BSC_FD_READ;
/* release all exisitng calls */
- gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC);
+ gsm0408_clear_all_trans(state->net, TRANS_CC);
/* flush the queue */
while (!llist_empty(&state->net->upqueue)) {
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
new file mode 100644
index 000000000..675f93202
--- /dev/null
+++ b/src/libmsc/msc_a.c
@@ -0,0 +1,1651 @@
+/* Code to manage a subscriber's MSC-A role */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/tdef.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/signal.h>
+
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/paging.h>
+#include <osmocom/msc/signal.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/ran_msg_a.h>
+#include <osmocom/msc/ran_msg_iu.h>
+#include <osmocom/msc/sgs_iface.h>
+#include <osmocom/msc/gsm_04_08.h>
+#include <osmocom/msc/gsm_09_11.h>
+#include <osmocom/msc/gsm_04_14.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/msc_ho.h>
+
+#define MSC_A_USE_WAIT_CLEAR_COMPLETE "wait-Clear-Complete"
+
+static struct osmo_fsm msc_a_fsm;
+
+static const struct osmo_tdef_state_timeout msc_a_fsm_timeouts[32] = {
+ [MSC_A_ST_VALIDATE_L3] = { .T = -1 },
+ [MSC_A_ST_AUTH_CIPH] = { .keep_timer = true },
+ [MSC_A_ST_WAIT_CLASSMARK_UPDATE] = { .keep_timer = true },
+ [MSC_A_ST_AUTHENTICATED] = { .keep_timer = true },
+ [MSC_A_ST_RELEASING] = { .T = -2 },
+ [MSC_A_ST_RELEASED] = { .T = -2 },
+};
+
+/* Transition to a state, using the T timer defined in msc_a_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define msc_a_state_chg(msc_a, state) \
+ osmo_tdef_fsm_inst_state_chg((msc_a)->c.fi, state, msc_a_fsm_timeouts, (msc_a)->c.ran->tdefs, 5)
+
+struct gsm_network *msc_a_net(const struct msc_a *msc_a)
+{
+ return msub_net(msc_a->c.msub);
+}
+
+struct vlr_subscr *msc_a_vsub(const struct msc_a *msc_a)
+{
+ return msub_vsub(msc_a->c.msub);
+}
+
+struct msc_i *msc_a_msc_i(const struct msc_a *msc_a)
+{
+ return msub_msc_i(msc_a->c.msub);
+}
+
+struct msc_t *msc_a_msc_t(const struct msc_a *msc_a)
+{
+ return msub_msc_t(msc_a->c.msub);
+}
+
+struct msc_a *msc_a_fi_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &msc_a_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+static void update_counters(struct osmo_fsm_inst *fi, bool conn_accepted)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct gsm_network *net = msc_a_net(msc_a);
+ switch (msc_a->complete_layer3_type) {
+ case COMPLETE_LAYER3_LU:
+ rate_ctr_inc(&net->msc_ctrs->ctr[
+ conn_accepted ? MSC_CTR_LOC_UPDATE_COMPLETED
+ : MSC_CTR_LOC_UPDATE_FAILED]);
+ break;
+ case COMPLETE_LAYER3_CM_SERVICE_REQ:
+ rate_ctr_inc(&net->msc_ctrs->ctr[
+ conn_accepted ? MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED
+ : MSC_CTR_CM_SERVICE_REQUEST_REJECTED]);
+ break;
+ case COMPLETE_LAYER3_PAGING_RESP:
+ rate_ctr_inc(&net->msc_ctrs->ctr[
+ conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED
+ : MSC_CTR_PAGING_RESP_REJECTED]);
+ break;
+ default:
+ break;
+ }
+}
+
+static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_accepted)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+
+ update_counters(fi, conn_accepted);
+
+ /* Trigger transactions that we paged for */
+ if (msc_a->complete_layer3_type == COMPLETE_LAYER3_PAGING_RESP) {
+ if (conn_accepted)
+ paging_response(msc_a);
+ else
+ paging_expired(vsub);
+ }
+
+ if (conn_accepted)
+ osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, msc_a_vsub(msc_a));
+
+ if (msc_a->complete_layer3_type == COMPLETE_LAYER3_LU)
+ msc_a_put(msc_a, MSC_A_USE_LOCATION_UPDATING);
+}
+
+bool msc_a_is_accepted(const struct msc_a *msc_a)
+{
+ if (!msc_a || !msc_a->c.fi)
+ return false;
+ return msc_a->c.fi->state == MSC_A_ST_AUTHENTICATED
+ || msc_a->c.fi->state == MSC_A_ST_COMMUNICATING;
+}
+
+bool msc_a_in_release(struct msc_a *msc_a)
+{
+ if (!msc_a)
+ return true;
+ if (msc_a->c.fi->state == MSC_A_ST_RELEASING)
+ return true;
+ if (msc_a->c.fi->state == MSC_A_ST_RELEASED)
+ return true;
+ return false;
+}
+
+static int msc_a_ran_dec(struct msc_a *msc_a, const struct an_apdu *an_apdu, enum msc_role from_role)
+{
+ int rc;
+ struct msc_a_ran_dec_data d = {
+ .from_role = from_role,
+ .an_apdu = an_apdu,
+ };
+ msc_a_get(msc_a, __func__);
+ rc = msc_role_ran_decode(msc_a->c.fi, an_apdu, msc_a_ran_decode_cb, &d);
+ msc_a_put(msc_a, __func__);
+ return rc;
+};
+
+static void msc_a_fsm_validate_l3(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+ const struct an_apdu *an_apdu;
+
+ switch (event) {
+ case MSC_A_EV_FROM_I_COMPLETE_LAYER_3:
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ an_apdu = data;
+ msc_a_ran_dec(msc_a, an_apdu, MSC_ROLE_I);
+ return;
+
+ case MSC_A_EV_COMPLETE_LAYER_3_OK:
+ msc_a_state_chg(msc_a, MSC_A_ST_AUTH_CIPH);
+ return;
+
+ case MSC_A_EV_MO_CLOSE:
+ case MSC_A_EV_CN_CLOSE:
+ evaluate_acceptance_outcome(fi, false);
+ /* fall through */
+ case MSC_A_EV_UNUSED:
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* Figure out whether to first send a Classmark Request to the MS to figure out algorithm support. */
+static bool msc_a_need_classmark_for_ciphering(struct msc_a *msc_a)
+{
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ int i = 0;
+ bool request_classmark = false;
+
+ /* Only on GERAN-A do we ever need Classmark Information for Ciphering. */
+ if (msc_a->c.ran->type != OSMO_RAT_GERAN_A)
+ return false;
+
+ for (i = 0; i < 8; i++) {
+ int supported;
+
+ /* A5/n permitted by osmo-msc.cfg? */
+ if (!(net->a5_encryption_mask & (1 << i)))
+ continue;
+
+ /* A5/n supported by MS? */
+ supported = osmo_gsm48_classmark_supports_a5(&vsub->classmark, i);
+ if (supported < 0) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "For A5/%d, we still need Classmark %d\n", i, -supported);
+ request_classmark = true;
+ }
+ }
+
+ return request_classmark;
+}
+
+static int msc_a_ran_enc_ciphering(struct msc_a *msc_a, bool umts_aka, bool retrieve_imeisv);
+
+/* VLR callback for ops.set_ciph_mode() */
+int msc_a_vlr_set_cipher_mode(void *_msc_a, bool umts_aka, bool retrieve_imeisv)
+{
+ struct msc_a *msc_a = _msc_a;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+
+ if (!msc_a || !vsub || !vsub->last_tuple) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Insufficient info to start ciphering\n");
+ return -EINVAL;
+ }
+
+ if (msc_a_need_classmark_for_ciphering(msc_a)) {
+ int rc;
+ struct ran_msg msg = {
+ .msg_type = RAN_MSG_CLASSMARK_REQUEST,
+ };
+ rc = msc_a_ran_down(msc_a, MSC_ROLE_I, &msg);
+ if (rc) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot send Classmark Request\n");
+ return -EIO;
+ }
+
+ msc_a->state_before_classmark_update = msc_a->c.fi->state;
+ msc_a->action_on_classmark_update = (struct msc_a_action_on_classmark_update){
+ .type = MSC_A_CLASSMARK_UPDATE_THEN_CIPHERING,
+ .ciphering = {
+ .umts_aka = umts_aka,
+ .retrieve_imeisv = retrieve_imeisv,
+ },
+ };
+ msc_a_state_chg(msc_a, MSC_A_ST_WAIT_CLASSMARK_UPDATE);
+ return 0;
+ }
+
+ return msc_a_ran_enc_ciphering(msc_a, umts_aka, retrieve_imeisv);
+}
+
+static int msc_a_ran_enc_ciphering(struct msc_a *msc_a, bool umts_aka, bool retrieve_imeisv)
+{
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct ran_msg msg;
+
+ if (!msc_a || !vsub || !vsub->last_tuple) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Insufficient info to start ciphering\n");
+ return -EINVAL;
+ }
+
+ msg = (struct ran_msg){
+ .msg_type = RAN_MSG_CIPHER_MODE_COMMAND,
+ .cipher_mode_command = {
+ .vec = vsub->last_tuple ? &vsub->last_tuple->vec : NULL,
+ .classmark = &vsub->classmark,
+ .geran = {
+ .umts_aka = umts_aka,
+ .retrieve_imeisv = retrieve_imeisv,
+ .a5_encryption_mask = net->a5_encryption_mask,
+
+ /* for ran_a.c to store the GERAN key that is actually used */
+ .chosen_key = &msc_a->geran_encr,
+ },
+ },
+ };
+
+ if (msc_a_ran_down(msc_a, MSC_ROLE_I, &msg)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Sending Cipher Mode Command failed\n");
+ /* Returning error to the VLR ops.set_ciph_mode() will cancel the attach. Other callers need to take
+ * care of the return value. */
+ return -EINVAL;
+ }
+
+ if (msc_a->geran_encr.key_len)
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "RAN encoding chose ciphering key %s\n",
+ osmo_hexdump_nospc(msc_a->geran_encr.key, msc_a->geran_encr.key_len));
+ return 0;
+}
+
+static void msc_a_fsm_auth_ciph(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ /* If accepted, transition the state, all other cases mean failure. */
+ switch (event) {
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ msc_a_ran_dec(msc_a, data, MSC_ROLE_I);
+ return;
+
+ case MSC_A_EV_AUTHENTICATED:
+ msc_a_state_chg(msc_a, MSC_A_ST_AUTHENTICATED);
+ return;
+
+ case MSC_A_EV_UNUSED:
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+ case MSC_A_EV_MO_CLOSE:
+ case MSC_A_EV_CN_CLOSE:
+ evaluate_acceptance_outcome(fi, false);
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_a_fsm_wait_classmark_update(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ switch (event) {
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ msc_a_ran_dec(msc_a, data, MSC_ROLE_I);
+ return;
+
+ case MSC_A_EV_CLASSMARK_UPDATE:
+ switch (msc_a->action_on_classmark_update.type) {
+ case MSC_A_CLASSMARK_UPDATE_THEN_CIPHERING:
+ msc_a_state_chg(msc_a, MSC_A_ST_AUTH_CIPH);
+ if (msc_a_ran_enc_ciphering(msc_a,
+ msc_a->action_on_classmark_update.ciphering.umts_aka,
+ msc_a->action_on_classmark_update.ciphering.retrieve_imeisv)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "After Classmark Update, still failed to send Cipher Mode Command\n");
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ }
+ return;
+
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Internal error: After Classmark Update, don't know what to do\n");
+ msc_a_state_chg(msc_a, msc_a->state_before_classmark_update);
+ return;
+ }
+
+ case MSC_A_EV_UNUSED:
+ /* Seems something detached / aborted in the middle of auth+ciph. */
+ evaluate_acceptance_outcome(fi, false);
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+ case MSC_A_EV_MO_CLOSE:
+ case MSC_A_EV_CN_CLOSE:
+ evaluate_acceptance_outcome(fi, false);
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static bool msc_a_fsm_has_active_transactions(struct osmo_fsm_inst *fi)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm_trans *trans;
+
+ if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_SILENT_CALL)) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: silent call still active\n", __func__);
+ return true;
+ }
+
+ if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_CC)) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still awaiting MO CC request after a CM Service Request\n",
+ __func__);
+ return true;
+ }
+ if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_SMS)) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still awaiting MO SMS after a CM Service Request\n",
+ __func__);
+ return true;
+ }
+ if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_SS)) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still awaiting MO SS after a CM Service Request\n",
+ __func__);
+ return true;
+ }
+
+ if (vsub && !llist_empty(&vsub->cs.requests)) {
+ struct paging_request *pr;
+ llist_for_each_entry(pr, &vsub->cs.requests, entry) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still active: %s\n", __func__, pr->label);
+ }
+ return true;
+ }
+
+ if ((trans = trans_has_conn(msc_a))) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "connection still has active transaction: %s\n",
+ trans_type_name(trans->type));
+ return true;
+ }
+
+ return false;
+}
+
+static void msc_a_fsm_authenticated_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+
+ /* Stop Location Update expiry for this subscriber. While the subscriber
+ * has an open connection the LU expiry timer must remain disabled.
+ * Otherwise we would kick the subscriber off the network when the timer
+ * expires e.g. during a long phone call.
+ * The LU expiry timer will restart once the connection is closed. */
+ if (vsub)
+ vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION;
+
+ evaluate_acceptance_outcome(fi, true);
+}
+
+static void msc_a_fsm_authenticated(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ switch (event) {
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST:
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ msc_a_ran_dec(msc_a, data, MSC_ROLE_I);
+ return;
+
+ case MSC_A_EV_COMPLETE_LAYER_3_OK:
+ /* When Authentication is off, we may already be in the Accepted state when the code
+ * evaluates the Compl L3. Simply ignore. This just cosmetically mutes the error log
+ * about the useless event. */
+ return;
+
+ case MSC_A_EV_TRANSACTION_ACCEPTED:
+ msc_a_state_chg(msc_a, MSC_A_ST_COMMUNICATING);
+ return;
+
+ case MSC_A_EV_MO_CLOSE:
+ case MSC_A_EV_CN_CLOSE:
+ case MSC_A_EV_UNUSED:
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* The MGW has given us a local IP address for the RAN side. Ready to start the Assignment of a voice channel. */
+static void msc_a_call_leg_ran_local_addr_available(struct msc_a *msc_a)
+{
+ struct ran_msg msg;
+ struct gsm_trans *cc_trans = msc_a->cc.active_trans;
+ struct gsm0808_channel_type channel_type;
+
+ /* Once a CI is known, we could also CRCX the CN side of the MGW endpoint, but it makes sense to wait for the
+ * codec to be determined by the Assignment Complete message, first. */
+
+ if (mncc_bearer_cap_to_channel_type(&channel_type, &cc_trans->bearer_cap)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose Channel Type from bearer capabilities\n");
+ /* FIXME: ERROR HANDLING */
+ return;
+ }
+
+ /* The RAN side RTP address is known, so the voice Assignment can commence. */
+ msg = (struct ran_msg){
+ .msg_type = RAN_MSG_ASSIGNMENT_COMMAND,
+ .assignment_command = {
+ .cn_rtp = &msc_a->cc.call_leg->rtp[RTP_TO_RAN]->local,
+ .channel_type = &channel_type,
+ },
+ };
+ if (msc_a_ran_down(msc_a, MSC_ROLE_I, &msg)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot send Assignment\n");
+ /* FIXME: ERROR HANDLING */
+ return;
+ }
+}
+
+static void msc_a_call_leg_cn_local_addr_available(struct msc_a *msc_a, struct gsm_trans *cc_trans)
+{
+ if (gsm48_tch_rtp_create(cc_trans)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot inform MNCC of RTP address\n");
+ /* FIXME: ERROR HANDLING */
+ return;
+ }
+}
+
+static struct gsm_trans *find_waiting_call(struct msc_a *msc_a)
+{
+ struct gsm_trans *trans;
+ struct gsm_network *net = msc_a_net(msc_a);
+
+ llist_for_each_entry(trans, &net->trans_list, entry) {
+ if (trans->msc_a != msc_a)
+ continue;
+ if (trans->type != TRANS_CC)
+ continue;
+ if (trans->msc_a->cc.active_trans == trans)
+ continue;
+ return trans;
+ }
+ return NULL;
+}
+
+static void msc_a_cleanup_rtp_streams(struct msc_a *msc_a, uint32_t event, void *data)
+{
+ struct rtp_stream *rtps;
+
+ switch (event) {
+ case MSC_EV_CALL_LEG_RTP_RELEASED:
+ rtps = data;
+ if (msc_a->cc.mncc_forwarding_to_remote_ran
+ && msc_a->cc.mncc_forwarding_to_remote_ran->rtps == rtps)
+ msc_a->cc.mncc_forwarding_to_remote_ran->rtps = NULL;
+ if (msc_a->ho.new_cell.mncc_forwarding_to_remote_ran
+ && msc_a->ho.new_cell.mncc_forwarding_to_remote_ran->rtps == rtps)
+ msc_a->ho.new_cell.mncc_forwarding_to_remote_ran->rtps = NULL;
+ return;
+
+ case MSC_EV_CALL_LEG_TERM:
+ msc_a->cc.call_leg = NULL;
+ if (msc_a->cc.mncc_forwarding_to_remote_ran)
+ msc_a->cc.mncc_forwarding_to_remote_ran->rtps = NULL;
+
+ if (msc_a->ho.new_cell.mncc_forwarding_to_remote_ran) {
+ fprintf(stderr, "FOCKEN %p\n", msc_a->ho.new_cell.mncc_forwarding_to_remote_ran->rtps);
+ msc_a->ho.new_cell.mncc_forwarding_to_remote_ran->rtps = NULL;
+ }
+ return;
+
+ case MSC_MNCC_EV_CALL_ENDED:
+ msc_a->cc.mncc_forwarding_to_remote_ran = NULL;
+ return;
+
+ default:
+ return;
+ }
+}
+
+static void msc_a_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct rtp_stream *rtps;
+ struct gsm_trans *waiting_trans;
+ struct an_apdu *an_apdu;
+
+ msc_a_cleanup_rtp_streams(msc_a, event, data);
+
+ switch (event) {
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST:
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ an_apdu = data;
+ msc_a_ran_dec(msc_a, an_apdu, MSC_ROLE_I);
+ return;
+
+ case MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE:
+ case MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE:
+ case MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST:
+ an_apdu = data;
+ msc_a_ran_dec(msc_a, an_apdu, MSC_ROLE_T);
+ return;
+
+ case MSC_A_EV_TRANSACTION_ACCEPTED:
+ /* no-op */
+ return;
+
+ case MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE:
+ rtps = data;
+ if (!rtps) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Invalid data for %s\n", osmo_fsm_event_name(fi->fsm, event));
+ return;
+ }
+ LOG_MSC_A(msc_a, LOGL_DEBUG,
+ "MGW endpoint's RTP address available for the CI %s: " OSMO_SOCKADDR_STR_FMT "\n",
+ rtp_direction_name(rtps->dir), OSMO_SOCKADDR_STR_FMT_ARGS(&rtps->local));
+ switch (rtps->dir) {
+ case RTP_TO_RAN:
+ msc_a_call_leg_ran_local_addr_available(msc_a);
+ return;
+ case RTP_TO_CN:
+ msc_a_call_leg_cn_local_addr_available(msc_a, rtps->for_trans);
+ return;
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Invalid data for %s\n", osmo_fsm_event_name(fi->fsm, event));
+ return;
+ }
+
+ case MSC_EV_CALL_LEG_RTP_COMPLETE:
+ /* Nothing to do. */
+ return;
+
+ case MSC_EV_CALL_LEG_RTP_RELEASED:
+ case MSC_MNCC_EV_CALL_ENDED:
+ /* Cleaned up above */
+ return;
+
+ case MSC_EV_CALL_LEG_TERM:
+ /* RTP streams cleaned up above */
+
+ msc_a_get(msc_a, __func__);
+ if (msc_a->cc.active_trans)
+ trans_free(msc_a->cc.active_trans);
+
+ /* If there is another call still waiting to be activated, this is the time when the mgcp_ctx is
+ * available again and the other call can start assigning. */
+ waiting_trans = find_waiting_call(msc_a);
+ if (waiting_trans) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "(ti %02x) Call waiting: starting Assignment\n",
+ waiting_trans->transaction_id);
+ msc_a_try_call_assignment(waiting_trans);
+ }
+ msc_a_put(msc_a, __func__);
+ return;
+
+ case MSC_A_EV_HANDOVER_REQUIRED:
+ msc_ho_start(msc_a, (struct ran_handover_required*)data);
+ return;
+
+ case MSC_A_EV_MO_CLOSE:
+ case MSC_A_EV_CN_CLOSE:
+ case MSC_A_EV_UNUSED:
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int msc_a_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct msc_a *msc_a = fi->priv;
+ if (msc_a_in_release(msc_a)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Timeout while releasing, discarding right now\n");
+ msc_a_put_all(msc_a, MSC_A_USE_WAIT_CLEAR_COMPLETE);
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASED);
+ } else {
+ enum gsm48_reject_value cause = GSM48_REJECT_CONGESTION;
+ osmo_fsm_inst_dispatch(fi, MSC_A_EV_CN_CLOSE, &cause);
+ }
+ return 0;
+}
+
+static void msc_a_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ int i;
+ char buf[128];
+ const char * const use_counts_to_cancel[] = {
+ MSC_A_USE_LOCATION_UPDATING,
+ MSC_A_USE_CM_SERVICE_CC,
+ MSC_A_USE_CM_SERVICE_SMS,
+ MSC_A_USE_CM_SERVICE_SS,
+ MSC_A_USE_PAGING_RESPONSE,
+ };
+
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Releasing: msc_a use is %s\n",
+ osmo_use_count_name_buf(buf, sizeof(buf), &msc_a->use_count));
+
+ if (vsub) {
+ vlr_subscr_get(vsub, __func__);
+
+ /* Cancel all VLR FSMs, if any */
+ vlr_subscr_cancel_attach_fsm(vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION);
+
+ /* The subscriber has no active connection anymore.
+ * Restart the periodic Location Update expiry timer for this subscriber. */
+ vlr_subscr_enable_expire_lu(vsub);
+ }
+
+ /* If we're closing in a middle of a trans, we need to clean up */
+ trans_conn_closed(msc_a);
+
+ call_leg_release(msc_a->cc.call_leg);
+
+ /* Cancel use counts for pending CM Service / Paging */
+ for (i = 0; i < ARRAY_SIZE(use_counts_to_cancel); i++) {
+ const char *use = use_counts_to_cancel[i];
+ int32_t count = osmo_use_count_by(&msc_a->use_count, use);
+ if (!count)
+ continue;
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Releasing: canceling still pending use: %s (%d)\n", use, count);
+ osmo_use_count_get_put(&msc_a->use_count, use, -count);
+ }
+
+ if (msc_a->c.ran->type == OSMO_RAT_EUTRAN_SGS) {
+ sgs_iface_tx_release(vsub);
+ /* In SGsAP there is no confirmation of a release. */
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASED);
+ } else {
+ struct ran_msg msg = {
+ .msg_type = RAN_MSG_CLEAR_COMMAND,
+ .clear_command = {
+ .csfb_ind = (vsub && vsub->sgs_fsm->state == SGS_UE_ST_ASSOCIATED),
+ },
+ };
+ msc_a_get(msc_a, MSC_A_USE_WAIT_CLEAR_COMPLETE);
+ msc_a_ran_down(msc_a, MSC_ROLE_I, &msg);
+ }
+
+ if (vsub)
+ vlr_subscr_put(vsub, __func__);
+}
+
+static void msc_a_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ msc_a_cleanup_rtp_streams(msc_a, event, data);
+
+ switch (event) {
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ msc_a_ran_dec(msc_a, data, MSC_ROLE_I);
+ return;
+
+ case MSC_A_EV_MO_CLOSE:
+ case MSC_A_EV_CN_CLOSE:
+ case MSC_A_EV_UNUSED:
+ /* Already releasing */
+ return;
+
+ case MSC_EV_CALL_LEG_RTP_RELEASED:
+ case MSC_EV_CALL_LEG_TERM:
+ case MSC_MNCC_EV_CALL_ENDED:
+ /* RTP streams cleaned up above */
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+
+static void msc_a_fsm_released_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = msc_a_fi_priv(fi);
+ char buf[128];
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Released: msc_a use is %s\n",
+ osmo_use_count_name_buf(buf, sizeof(buf), &msc_a->use_count));
+ if (osmo_use_count_total(&msc_a->use_count) == 0)
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
+}
+
+static void msc_a_fsm_released(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ if (event == MSC_A_EV_UNUSED)
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
+}
+
+void msc_a_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ //struct msc_a *a = msc_a_fi_priv(fi);
+ switch (event) {
+
+ default:
+ return;
+ }
+}
+
+void msc_a_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_a *msc_a = msc_a_fi_priv(fi);
+
+ trans_conn_closed(msc_a);
+
+ if (msc_a_fsm_has_active_transactions(fi))
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Deallocating active transactions failed\n");
+
+ LOG_MSC_A_CAT(msc_a, DREF, LOGL_DEBUG, "max total use count was %d\n", msc_a->max_total_use_count);
+}
+
+const struct value_string msc_a_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MSC_REMOTE_EV_RX_GSUP),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_COMPLETE),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_RELEASED),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_TERM),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_NEED_LOCAL_RTP),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_PROCEEDING),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_COMPLETE),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_ENDED),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_I_COMPLETE_LAYER_3),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE),
+ OSMO_VALUE_STRING(MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST),
+ OSMO_VALUE_STRING(MSC_A_EV_COMPLETE_LAYER_3_OK),
+ OSMO_VALUE_STRING(MSC_A_EV_CLASSMARK_UPDATE),
+ OSMO_VALUE_STRING(MSC_A_EV_AUTHENTICATED),
+ OSMO_VALUE_STRING(MSC_A_EV_TRANSACTION_ACCEPTED),
+ OSMO_VALUE_STRING(MSC_A_EV_CN_CLOSE),
+ OSMO_VALUE_STRING(MSC_A_EV_MO_CLOSE),
+ OSMO_VALUE_STRING(MSC_A_EV_UNUSED),
+ OSMO_VALUE_STRING(MSC_A_EV_HANDOVER_REQUIRED),
+ OSMO_VALUE_STRING(MSC_A_EV_HANDOVER_END),
+ {}
+};
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_a_fsm_states[] = {
+ [MSC_A_ST_VALIDATE_L3] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_VALIDATE_L3),
+ .in_event_mask = 0
+ | S(MSC_A_EV_FROM_I_COMPLETE_LAYER_3)
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_COMPLETE_LAYER_3_OK)
+ | S(MSC_A_EV_MO_CLOSE)
+ | S(MSC_A_EV_CN_CLOSE)
+ | S(MSC_A_EV_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_VALIDATE_L3)
+ | S(MSC_A_ST_AUTH_CIPH)
+ | S(MSC_A_ST_RELEASING)
+ ,
+ .action = msc_a_fsm_validate_l3,
+ },
+ [MSC_A_ST_AUTH_CIPH] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_AUTH_CIPH),
+ .in_event_mask = 0
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_AUTHENTICATED)
+ | S(MSC_A_EV_MO_CLOSE)
+ | S(MSC_A_EV_CN_CLOSE)
+ | S(MSC_A_EV_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_WAIT_CLASSMARK_UPDATE)
+ | S(MSC_A_ST_AUTHENTICATED)
+ | S(MSC_A_ST_RELEASING)
+ ,
+ .action = msc_a_fsm_auth_ciph,
+ },
+ [MSC_A_ST_WAIT_CLASSMARK_UPDATE] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_WAIT_CLASSMARK_UPDATE),
+ .in_event_mask = 0
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_CLASSMARK_UPDATE)
+ | S(MSC_A_EV_MO_CLOSE)
+ | S(MSC_A_EV_CN_CLOSE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_AUTH_CIPH)
+ | S(MSC_A_ST_RELEASING)
+ ,
+ .action = msc_a_fsm_wait_classmark_update,
+ },
+ [MSC_A_ST_AUTHENTICATED] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_AUTHENTICATED),
+ /* allow everything to release for any odd behavior */
+ .in_event_mask = 0
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_TRANSACTION_ACCEPTED)
+ | S(MSC_A_EV_MO_CLOSE)
+ | S(MSC_A_EV_CN_CLOSE)
+ | S(MSC_A_EV_UNUSED)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_RELEASING)
+ | S(MSC_A_ST_COMMUNICATING)
+ ,
+ .onenter = msc_a_fsm_authenticated_enter,
+ .action = msc_a_fsm_authenticated,
+ },
+ [MSC_A_ST_COMMUNICATING] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_COMMUNICATING),
+ /* allow everything to release for any odd behavior */
+ .in_event_mask = 0
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE)
+ | S(MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE)
+ | S(MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_TRANSACTION_ACCEPTED)
+ | S(MSC_A_EV_MO_CLOSE)
+ | S(MSC_A_EV_CN_CLOSE)
+ | S(MSC_A_EV_UNUSED)
+ | S(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE)
+ | S(MSC_EV_CALL_LEG_RTP_COMPLETE)
+ | S(MSC_EV_CALL_LEG_RTP_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ | S(MSC_A_EV_HANDOVER_REQUIRED)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_RELEASING)
+ ,
+ .action = msc_a_fsm_communicating,
+ },
+ [MSC_A_ST_RELEASING] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_RELEASING),
+ .in_event_mask = 0
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_UNUSED)
+ | S(MSC_EV_CALL_LEG_RTP_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_RELEASED)
+ ,
+ .onenter = msc_a_fsm_releasing_onenter,
+ .action = msc_a_fsm_releasing,
+ },
+ [MSC_A_ST_RELEASED] = {
+ .name = OSMO_STRINGIFY(MSC_A_ST_RELEASED),
+ .in_event_mask = 0
+ | S(MSC_A_EV_UNUSED)
+ ,
+ .onenter = msc_a_fsm_released_onenter,
+ .action = msc_a_fsm_released,
+ },
+};
+
+static struct osmo_fsm msc_a_fsm = {
+ .name = "msc_a",
+ .states = msc_a_fsm_states,
+ .num_states = ARRAY_SIZE(msc_a_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msc_a_fsm_event_names,
+ .allstate_action = msc_a_fsm_allstate_action,
+ .allstate_event_mask = 0
+ ,
+ .timer_cb = msc_a_fsm_timer_cb,
+ .cleanup = msc_a_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msc_a_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&msc_a_fsm) == 0);
+}
+
+static int msc_a_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
+{
+ struct msc_a *msc_a = e->use_count->talloc_object;
+ char buf[128];
+ int32_t total;
+ int level;
+
+ if (!e->use)
+ return -EINVAL;
+
+ total = osmo_use_count_total(&msc_a->use_count);
+
+ if (total == 0
+ || (total == 1 && old_use_count == 0 && e->count == 1))
+ level = LOGL_INFO;
+ else
+ level = LOGL_DEBUG;
+
+ LOG_MSC_A_CAT_SRC(msc_a, DREF, level, file, line, "%s %s: now used by %s\n",
+ (e->count - old_use_count) > 0? "+" : "-", e->use,
+ osmo_use_count_name_buf(buf, sizeof(buf), &msc_a->use_count));
+
+ if (e->count < 0)
+ return -ERANGE;
+
+ msc_a->max_total_use_count = OSMO_MAX(msc_a->max_total_use_count, total);
+
+ if (total == 0)
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_UNUSED, NULL);
+ return 0;
+}
+
+struct msc_a *msc_a_alloc(struct msub *msub, struct ran_infra *ran)
+{
+ struct msc_a *msc_a = msub_role_alloc(msub, MSC_ROLE_A, &msc_a_fsm, struct msc_a, ran);
+ msc_a->use_count = (struct osmo_use_count){
+ .talloc_object = msc_a,
+ .use_cb = msc_a_use_cb,
+ };
+ osmo_use_count_make_static_entries(&msc_a->use_count, msc_a->use_count_buf, ARRAY_SIZE(msc_a->use_count_buf));
+ /* Start timeout for first state */
+ msc_a_state_chg(msc_a, MSC_A_ST_VALIDATE_L3);
+ return msc_a;
+}
+
+bool msc_a_is_establishing_auth_ciph(const struct msc_a *msc_a)
+{
+ if (!msc_a || !msc_a->c.fi)
+ return false;
+ return msc_a->c.fi->state == MSC_A_ST_AUTH_CIPH;
+}
+
+const struct value_string complete_layer3_type_names[] = {
+ { COMPLETE_LAYER3_NONE, "NONE" },
+ { COMPLETE_LAYER3_LU, "LU" },
+ { COMPLETE_LAYER3_CM_SERVICE_REQ, "CM_SERVICE_REQ" },
+ { COMPLETE_LAYER3_PAGING_RESP, "PAGING_RESP" },
+ { 0, NULL }
+};
+
+#define _msc_a_update_id(MSC_A, FMT, ARGS ...) \
+ do { \
+ if (osmo_fsm_inst_update_id_f(msc_a->c.fi, FMT ":%s:%s", \
+ ## ARGS, \
+ msub_ran_conn_name(msc_a->c.msub), \
+ complete_layer3_type_name(msc_a->complete_layer3_type)) \
+ == 0) { \
+ struct vlr_subscr *_vsub = msc_a_vsub(MSC_A); \
+ if (_vsub) { \
+ if (_vsub->lu_fsm) \
+ osmo_fsm_inst_update_id(_vsub->lu_fsm, (MSC_A)->c.fi->id); \
+ if (_vsub->auth_fsm) \
+ osmo_fsm_inst_update_id(_vsub->auth_fsm, (MSC_A)->c.fi->id); \
+ if (_vsub->proc_arq_fsm) \
+ osmo_fsm_inst_update_id(_vsub->proc_arq_fsm, (MSC_A)->c.fi->id); \
+ } \
+ LOG_MSC_A(MSC_A, LOGL_DEBUG, "Updated ID\n"); \
+ } \
+ /* otherwise osmo_fsm_inst_update_id_f() will log an error. */ \
+ } while (0)
+
+
+/* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */
+void msc_a_update_id_from_mi(struct msc_a *msc_a, const uint8_t mi[], uint8_t mi_len)
+{
+ _msc_a_update_id(msc_a, "%s", osmo_mi_name(mi, mi_len));
+}
+
+/* Update msc_a->fi id string from current msc_a->vsub and msc_a->complete_layer3_type. */
+void msc_a_update_id(struct msc_a *msc_a)
+{
+ _msc_a_update_id(msc_a, "%s", vlr_subscr_name(msc_a_vsub(msc_a)));
+}
+
+/* Iterate all msc_a instances that are relevant for this subscriber, and update FSM ID strings for all of the FSM
+ * instances. */
+void msc_a_update_id_for_vsub(struct vlr_subscr *for_vsub)
+{
+ struct msub *msub;
+ llist_for_each_entry(msub, &msub_list, entry) {
+ struct vlr_subscr *vsub = msub_vsub(msub);
+ if (vsub != for_vsub)
+ continue;
+ msc_a_update_id(msub_msc_a(msub));
+ }
+}
+
+static bool msg_is_initially_permitted(const struct gsm48_hdr *hdr)
+{
+ uint8_t pdisc = gsm48_hdr_pdisc(hdr);
+ uint8_t msg_type = gsm48_hdr_msg_type(hdr);
+
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ switch (msg_type) {
+ case GSM48_MT_MM_LOC_UPD_REQUEST:
+ case GSM48_MT_MM_CM_SERV_REQ:
+ case GSM48_MT_MM_CM_REEST_REQ:
+ case GSM48_MT_MM_AUTH_RESP:
+ case GSM48_MT_MM_AUTH_FAIL:
+ case GSM48_MT_MM_ID_RESP:
+ case GSM48_MT_MM_TMSI_REALL_COMPL:
+ case GSM48_MT_MM_IMSI_DETACH_IND:
+ return true;
+ default:
+ break;
+ }
+ break;
+ case GSM48_PDISC_RR:
+ switch (msg_type) {
+ /* GSM48_MT_RR_CIPH_M_COMPL is actually handled in bssmap_rx_ciph_compl() and gets redirected in the
+ * BSSAP layer to ran_conn_cipher_mode_compl() (before this here is reached) */
+ case GSM48_MT_RR_PAG_RESP:
+ case GSM48_MT_RR_CIPH_M_COMPL:
+ return true;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/* Main entry point for GSM 04.08/44.008 Layer 3 data (e.g. from the BSC). */
+int msc_a_up_l3(struct msc_a *msc_a, struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ int rc;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ int is_r99;
+
+ OSMO_ASSERT(msg->l3h);
+ OSMO_ASSERT(msg);
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+
+ LOG_MSC_A_CAT(msc_a, DRLL, LOGL_DEBUG, "Dispatching 04.08 message: %s %s\n",
+ gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh)));
+
+ /* To evaluate the 3GPP TS 24.007 Duplicate Detection, we need Classmark information on whether the MS is R99
+ * capable. If the subscriber is already actively connected, the Classmark information is stored with the
+ * vlr_subscr. Otherwise, this *must* be a Complete Layer 3 with Classmark info. */
+ if (vsub)
+ is_r99 = osmo_gsm48_classmark_is_r99(&vsub->classmark) ? 1 : 0;
+ else
+ is_r99 = compl_l3_msg_is_r99(msg);
+
+ if (is_r99 < 0) {
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "No Classmark Information, dropping non-Complete-Layer3 message: %s\n",
+ gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh)));
+ return -EACCES;
+ }
+
+ if (is_r99 >= 0
+ && ran_dec_dtap_undup_is_duplicate(msc_a->c.fi, msc_a->n_sd_next, is_r99 ? true : false, msg)) {
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Dropping duplicate message"
+ " (3GPP TS 24.007 11.2.3.2 Message Type Octet / Duplicate Detection)\n");
+ return 0;
+ }
+
+ if (!msc_a_is_accepted(msc_a)
+ && !msg_is_initially_permitted(gh)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Message not permitted for initial conn: %s\n",
+ gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh)));
+ return -EACCES;
+ }
+
+ if (vsub && vsub->cs.attached_via_ran != msc_a->c.ran->type) {
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Illegal situation: RAN type mismatch:"
+ " attached via %s, received message via %s\n",
+ osmo_rat_type_name(vsub->cs.attached_via_ran),
+ osmo_rat_type_name(msc_a->c.ran->type));
+ return -EACCES;
+ }
+
+#if 0
+ if (silent_call_reroute(conn, msg))
+ return silent_call_rx(conn, msg);
+#endif
+
+ switch (pdisc) {
+ case GSM48_PDISC_CC:
+ rc = gsm0408_rcv_cc(msc_a, msg);
+ break;
+ case GSM48_PDISC_MM:
+ rc = gsm0408_rcv_mm(msc_a, msg);
+ break;
+ case GSM48_PDISC_RR:
+ rc = gsm0408_rcv_rr(msc_a, msg);
+ break;
+ case GSM48_PDISC_SMS:
+ rc = gsm0411_rcv_sms(msc_a, msg);
+ break;
+ case GSM48_PDISC_MM_GPRS:
+ case GSM48_PDISC_SM_GPRS:
+ LOG_MSC_A_CAT(msc_a, DRLL, LOGL_NOTICE, "Unimplemented "
+ "GSM 04.08 discriminator 0x%02x\n", pdisc);
+ rc = -ENOTSUP;
+ break;
+ case GSM48_PDISC_NC_SS:
+ rc = gsm0911_rcv_nc_ss(msc_a, msg);
+ break;
+ case GSM48_PDISC_TEST:
+ rc = gsm0414_rcv_test(msc_a, msg);
+ break;
+ default:
+ LOG_MSC_A_CAT(msc_a, DRLL, LOGL_NOTICE, "Unknown "
+ "GSM 04.08 discriminator 0x%02x\n", pdisc);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static void msc_a_up_call_assignment_complete(struct msc_a *msc_a, const struct ran_msg *ac)
+{
+ struct gsm_trans *cc_trans = msc_a->cc.active_trans;
+ struct rtp_stream *rtps_to_ran = msc_a->cc.call_leg ? msc_a->cc.call_leg->rtp[RTP_TO_RAN] : NULL;
+
+ if (!rtps_to_ran) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but no RTP stream is set up\n");
+ return;
+ }
+ if (!cc_trans) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but CC transaction is active\n");
+ return;
+ }
+
+ /* Update RAN-side endpoint CI: */
+ rtp_stream_set_codec(rtps_to_ran, ac->assignment_complete.codec);
+ rtp_stream_set_remote_addr(rtps_to_ran, &ac->assignment_complete.remote_rtp);
+ rtp_stream_commit(rtps_to_ran);
+
+ /* Setup CN side endpoint CI:
+ * Now that
+ * - the first CI has been created and a definitive endpoint name is assigned to the call_leg's MGW
+ * endpoint,
+ * - the Assignment has chosen a speech codec
+ * go on to create the CN side RTP stream's CI. */
+ if (call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_CN, cc_trans->callref, cc_trans,
+ &ac->assignment_complete.codec, NULL)) {
+ LOG_MSC_A_CAT(msc_a, DCC, LOGL_ERROR, "Error creating MGW CI towards CN\n");
+ call_leg_release(msc_a->cc.call_leg);
+ return;
+ }
+}
+
+static void msc_a_up_call_assignment_failure(struct msc_a *msc_a, const struct ran_msg *af)
+{
+ struct gsm_trans *trans;
+
+ /* For a normal voice call, there will be an rtp_stream FSM. */
+ if (msc_a->cc.call_leg && msc_a->cc.call_leg->rtp[RTP_TO_RAN]) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Assignment Failure, releasing call\n");
+ rtp_stream_release(msc_a->cc.call_leg->rtp[RTP_TO_RAN]);
+ return;
+ }
+
+ /* Otherwise, a silent call might be active */
+ trans = trans_find_by_type(msc_a, TRANS_SILENT_CALL);
+ if (trans) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Assignment Failure, releasing silent call\n");
+ trans_free(trans);
+ return;
+ }
+
+ /* Neither a voice call nor silent call assignment. Assume the worst and detach. */
+ msc_a_release_cn(msc_a);
+}
+
+static void msc_a_up_classmark_update(struct msc_a *msc_a, const struct osmo_gsm48_classmark *classmark,
+ struct osmo_gsm48_classmark *dst)
+{
+ if (!dst) {
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+
+ if (!vsub)
+ dst = &msc_a->temporary_classmark;
+ else
+ dst = &vsub->classmark;
+ }
+
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "A5 capabilities recived from Classmark Update: %s\n",
+ osmo_gsm48_classmark_a5_name(classmark));
+ osmo_gsm48_classmark_update(dst, classmark);
+
+ /* bump subscr conn FSM in case it is waiting for a Classmark Update */
+ if (msc_a->c.fi->state == MSC_A_ST_WAIT_CLASSMARK_UPDATE)
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_CLASSMARK_UPDATE, NULL);
+}
+
+static void msc_a_up_sapi_n_reject(struct msc_a *msc_a, const struct ran_msg *msg)
+{
+ int sapi = msg->sapi_n_reject.dlci & 0x7;
+ if (sapi == UM_SAPI_SMS)
+ gsm411_sapi_n_reject(msc_a);
+}
+
+static int msc_a_up_ho(struct msc_a *msc_a, const struct msc_a_ran_dec_data *d, uint32_t ho_fi_event)
+{
+ if (!msc_a->ho.fi) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Handover message, but no Handover ongoing: %s\n", d->ran_dec->msg_name);
+ return -EINVAL;
+ }
+ return osmo_fsm_inst_dispatch(msc_a->ho.fi, ho_fi_event, (void*)d);
+}
+
+int msc_a_ran_dec_from_msc_i(struct msc_a *msc_a, struct msc_a_ran_dec_data *d)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ const struct ran_msg *msg = d->ran_dec;
+ int rc = -99;
+
+ switch (msg->msg_type) {
+
+ case RAN_MSG_COMPL_L3:
+ msc_a->via_cell = (struct osmo_cell_global_id){
+ .lai.plmn = msc_a_net(msc_a)->plmn,
+ };
+ gsm0808_cell_id_to_cgi(&msc_a->via_cell, msg->compl_l3.cell_id);
+ rc = msc_a_up_l3(msc_a, msg->compl_l3.msg);
+ if (!rc) {
+ struct ran_conn *conn = msub_ran_conn(msc_a->c.msub);
+ if (conn)
+ ran_peer_cells_seen_add(conn->ran_peer, msg->compl_l3.cell_id);
+ }
+ break;
+
+ case RAN_MSG_DTAP:
+ rc = msc_a_up_l3(msc_a, msg->dtap);
+ break;
+
+ case RAN_MSG_CLEAR_REQUEST:
+ rc = osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_MO_CLOSE, NULL);
+ break;
+
+ case RAN_MSG_CLEAR_COMPLETE:
+ switch (msc_a->c.fi->state) {
+ case MSC_A_ST_RELEASING:
+ msc_a_put_all(msc_a, MSC_A_USE_WAIT_CLEAR_COMPLETE);
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASED);
+ break;
+ case MSC_A_ST_RELEASED:
+ break;
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Received Clear Complete event, but did not send Clear Command\n");
+ msc_a_state_chg(msc_a, MSC_A_ST_RELEASING);
+ break;
+ }
+ rc = 0;
+ break;
+
+ case RAN_MSG_CLASSMARK_UPDATE:
+ msc_a_up_classmark_update(msc_a, msg->classmark_update.classmark, NULL);
+ rc = 0;
+ break;
+
+ case RAN_MSG_CIPHER_MODE_COMPLETE:
+ /* Remember what Ciphering was negotiated (e.g. for Handover) */
+ if (msg->cipher_mode_complete.alg_id) {
+ msc_a->geran_encr.alg_id = msg->cipher_mode_complete.alg_id;
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Cipher Mode Complete: chosen encryption algorithm: A5/%u\n",
+ msc_a->geran_encr.alg_id - 1);
+ };
+ vlr_subscr_rx_ciph_res(vsub, VLR_CIPH_COMPL);
+ rc = 0;
+ break;
+
+ case RAN_MSG_CIPHER_MODE_REJECT:
+ vlr_subscr_rx_ciph_res(vsub, VLR_CIPH_REJECT);
+ rc = 0;
+ break;
+
+ case RAN_MSG_ASSIGNMENT_COMPLETE:
+ msc_a_up_call_assignment_complete(msc_a, msg);
+ rc = 0;
+ break;
+
+ case RAN_MSG_ASSIGNMENT_FAILURE:
+ msc_a_up_call_assignment_failure(msc_a, msg);
+ rc = 0;
+ break;
+
+ case RAN_MSG_SAPI_N_REJECT:
+ msc_a_up_sapi_n_reject(msc_a, msg);
+ rc = 0;
+ break;
+
+ case RAN_MSG_HANDOVER_PERFORMED:
+ /* The BSS lets us know that a handover happened within the BSS, which doesn't concern us. */
+ LOG_MSC_A(msc_a, LOGL_ERROR, "'Handover Performed' handling not implemented\n");
+ break;
+
+ case RAN_MSG_HANDOVER_REQUIRED:
+ /* The BSS lets us know that it wants to handover to a different cell */
+ rc = osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_HANDOVER_REQUIRED, (void*)&msg->handover_required);
+ break;
+
+ case RAN_MSG_HANDOVER_FAILURE:
+ rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_FAILURE);
+ break;
+
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Message from MSC-I not implemented: %s\n", ran_msg_type_name(msg->msg_type));
+ rc = -ENOTSUP;
+ break;
+ }
+ return rc;
+}
+
+static int msc_a_ran_dec_from_msc_t(struct msc_a *msc_a, struct msc_a_ran_dec_data *d)
+{
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+ int rc = -99;
+
+ if (!msc_t) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx message from MSC-T role, but I have no active MSC-T role.\n");
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(d->ran_dec);
+
+ switch (d->ran_dec->msg_type) {
+
+ case RAN_MSG_CLEAR_REQUEST:
+ rc = osmo_fsm_inst_dispatch(msc_t->c.fi, MSC_T_EV_MO_CLOSE, NULL);
+ break;
+
+ case RAN_MSG_CLEAR_COMPLETE:
+ rc = osmo_fsm_inst_dispatch(msc_t->c.fi, MSC_T_EV_CLEAR_COMPLETE, NULL);
+ break;
+
+ case RAN_MSG_CLASSMARK_UPDATE:
+ msc_a_up_classmark_update(msc_a, d->ran_dec->classmark_update.classmark, &msc_t->classmark);
+ rc = 0;
+ break;
+
+ case RAN_MSG_HANDOVER_REQUEST_ACK:
+ /* new BSS accepts Handover */
+ rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_REQUEST_ACK);
+ break;
+
+ case RAN_MSG_HANDOVER_DETECT:
+ /* new BSS signals the MS is DETECTed on the new lchan */
+ rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_DETECT);
+ break;
+
+ case RAN_MSG_HANDOVER_COMPLETE:
+ /* new BSS signals the MS has fully moved to the new lchan */
+ rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_COMPLETE);
+ break;
+
+ case RAN_MSG_HANDOVER_FAILURE:
+ rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_FAILURE);
+ break;
+
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Message from MSC-T not implemented: %s\n",
+ ran_msg_type_name(d->ran_dec->msg_type));
+ rc = -ENOTSUP;
+ break;
+ }
+ return rc;
+}
+
+int msc_a_ran_decode_cb(struct osmo_fsm_inst *msc_a_fi, void *data, const struct ran_msg *msg)
+{
+ struct msc_a *msc_a = msc_a_fi_priv(msc_a_fi);
+ struct msc_a_ran_dec_data *d = data;
+ int rc = -99;
+
+ d->ran_dec = msg;
+
+ switch (d->from_role) {
+ case MSC_ROLE_I:
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "RAN decode: %s\n", msg->msg_name ? : ran_msg_type_name(msg->msg_type));
+ rc = msc_a_ran_dec_from_msc_i(msc_a, d);
+ break;
+
+ case MSC_ROLE_T:
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "RAN decode from MSC-T: %s\n",
+ msg->msg_name ? : ran_msg_type_name(msg->msg_type));
+ rc = msc_a_ran_dec_from_msc_t(msc_a, d);
+ break;
+
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Message from invalid role %s: %s\n", msc_role_name(d->from_role),
+ ran_msg_type_name(msg->msg_type));
+ return -ENOTSUP;
+ }
+
+ if (rc)
+ LOG_MSC_A(msc_a, LOGL_ERROR, "RAN decode error (rc=%d) for %s from %s\n", rc, ran_msg_type_name(msg->msg_type),
+ msc_role_name(d->from_role));
+ return rc;
+}
+
+/* Your typical DTAP via FORWARD_ACCESS_SIGNALLING_REQUEST */
+int _msc_a_ran_down(struct msc_a *msc_a, enum msc_role to_role, const struct ran_msg *ran_msg,
+ const char *file, int line)
+{
+ return _msc_a_msg_down(msc_a, to_role, msub_role_to_role_event(msc_a->c.msub, MSC_ROLE_A, to_role),
+ ran_msg, file, line);
+}
+
+/* To transmit more complex events than just FORWARD_ACCESS_SIGNALLING_REQUEST, e.g. an
+ * MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST */
+int _msc_a_msg_down(struct msc_a *msc_a, enum msc_role to_role, uint32_t to_role_event,
+ const struct ran_msg *ran_msg,
+ const char *file, int line)
+{
+ struct an_apdu an_apdu = {
+ .an_proto = msc_a->c.ran->an_proto,
+ .msg = msc_role_ran_encode(msc_a->c.fi, ran_msg),
+ };
+ int rc;
+ if (!an_apdu.msg)
+ return -EIO;
+ rc = _msub_role_dispatch(msc_a->c.msub, to_role, to_role_event, &an_apdu, file, line);
+ msgb_free(an_apdu.msg);
+ return rc;
+}
+
+int msc_a_tx_dtap_to_i(struct msc_a *msc_a, struct msgb *dtap)
+{
+ struct ran_msg ran_msg;
+
+ if (msc_a->c.ran->type == OSMO_RAT_EUTRAN_SGS) {
+ /* The SGs connection to the MME always is at the MSC-A. */
+ return sgs_iface_tx_dtap_ud(msc_a, dtap);
+ }
+
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_DTAP,
+ .dtap = dtap,
+ };
+ return msc_a_ran_down(msc_a, MSC_ROLE_I, &ran_msg);
+}
+
+struct msc_a *msc_a_for_vsub(const struct vlr_subscr *vsub, bool valid_conn_only)
+{
+ struct msc_a *msc_a = msub_msc_a(msub_for_vsub(vsub));
+ if (valid_conn_only && !msc_a_is_accepted(msc_a))
+ return NULL;
+ return msc_a;
+}
+
+int msc_tx_common_id(struct msc_a *msc_a, enum msc_role to_role)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct ran_msg msg = {
+ .msg_type = RAN_MSG_COMMON_ID,
+ .common_id = {
+ .imsi = vsub->imsi,
+ },
+ };
+
+ return msc_a_ran_down(msc_a, to_role, &msg);
+}
+
+static int msc_a_start_assignment(struct msc_a *msc_a, struct gsm_trans *cc_trans)
+{
+ struct call_leg *cl = msc_a->cc.call_leg;
+
+ OSMO_ASSERT(!msc_a->cc.active_trans);
+ msc_a->cc.active_trans = cc_trans;
+
+ OSMO_ASSERT(cc_trans && cc_trans->type == TRANS_CC);
+
+ if (!cl) {
+ cl = msc_a->cc.call_leg = call_leg_alloc(msc_a->c.fi,
+ MSC_EV_CALL_LEG_TERM,
+ MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
+ MSC_EV_CALL_LEG_RTP_COMPLETE,
+ MSC_EV_CALL_LEG_RTP_RELEASED);
+ OSMO_ASSERT(cl);
+
+ /* HACK: We put the connection in loopback mode at the beginnig to
+ * trick the hNodeB into doing the IuUP negotiation with itself.
+ * This is a hack we need because osmo-mgw does not support IuUP yet, see OS#2459. */
+ if (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU)
+ cl->crcx_conn_mode[RTP_TO_RAN] = MGCP_CONN_LOOPBACK;
+ }
+
+ /* This will lead to either MSC_EV_CALL_LEG_LOCAL_ADDR_AVAILABLE or MSC_EV_CALL_LEG_TERM.
+ * If the local address is already known, then immediately trigger. */
+ if (call_leg_local_ip(cl, RTP_TO_RAN))
+ return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_RAN]);
+ else
+ return call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_RAN, cc_trans->callref, cc_trans, NULL, NULL);
+}
+
+int msc_a_try_call_assignment(struct gsm_trans *cc_trans)
+{
+ struct msc_a *msc_a = cc_trans->msc_a;
+ OSMO_ASSERT(cc_trans->type == TRANS_CC);
+
+ if (msc_a->cc.active_trans == cc_trans) {
+ /* Assignment for this trans already started earlier. */
+ return 0;
+ }
+
+ if (msc_a->cc.active_trans) {
+ LOG_MSC_A(msc_a, LOGL_INFO, "Another call is already ongoing, not assigning yet\n");
+ return 0;
+ }
+
+ LOG_MSC_A(msc_a, LOGL_DEBUG, "Starting call assignment\n");
+ return msc_a_start_assignment(msc_a, cc_trans);
+}
+
+const char *msc_a_cm_service_type_to_use(enum osmo_cm_service_type cm_service_type)
+{
+ switch (cm_service_type) {
+ case GSM48_CMSERV_MO_CALL_PACKET:
+ case GSM48_CMSERV_EMERGENCY:
+ return MSC_A_USE_CM_SERVICE_CC;
+
+ case GSM48_CMSERV_SMS:
+ return MSC_A_USE_CM_SERVICE_SMS;
+
+ case GSM48_CMSERV_SUP_SERV:
+ return MSC_A_USE_CM_SERVICE_SS;
+
+ default:
+ return NULL;
+ }
+}
+
+void msc_a_release_cn(struct msc_a *msc_a)
+{
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_CN_CLOSE, NULL);
+}
+
+void msc_a_release_mo(struct msc_a *msc_a, enum gsm48_gsm_cause gsm_cause)
+{
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_MO_CLOSE, NULL);
+}
diff --git a/src/libmsc/msc_a_remote.c b/src/libmsc/msc_a_remote.c
new file mode 100644
index 000000000..84eff0730
--- /dev/null
+++ b/src/libmsc/msc_a_remote.c
@@ -0,0 +1,392 @@
+/* The MSC-A role implementation variant that forwards requests to/from a remote MSC. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/gsup.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/msc_a_remote.h>
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/e_link.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/ran_peer.h>
+
+static struct osmo_fsm msc_a_remote_fsm;
+
+static struct msc_a *msc_a_remote_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &msc_a_remote_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+/* The idea is that this msc_a role is event-compatible to the "real" msc_a.c FSM, but instead of acting on the events
+ * directly, it forwards the events to a remote MSC-A role, via E-over-GSUP.
+ *
+ * [MSC-A---------------------] [MSC-B---------------------]
+ * msc_a <-- msc_{i,t}_remote <---GSUP---- msc_a_remote <-- msc_{i,t} <--BSSMAP--- [BSS]
+ * ^you are here
+ */
+static int msc_a_remote_msg_up_to_remote_msc(struct msc_a *msc_a,
+ enum msc_role from_role,
+ enum osmo_gsup_message_type message_type,
+ struct an_apdu *an_apdu)
+{
+ struct osmo_gsup_message m;
+ struct e_link *e = msc_a->c.remote_to;
+
+ if (!e) {
+ LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "No E link to remote MSC, cannot send AN-APDU\n");
+ return -1;
+ }
+
+ if (e_prep_gsup_msg(e, &m)) {
+ LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Error composing E-interface GSUP message\n");
+ return -1;
+ }
+ m.message_type = message_type;
+ if (an_apdu) {
+ if (gsup_msg_assign_an_apdu(&m, an_apdu)) {
+ LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Error composing E-interface GSUP message\n");
+ return -1;
+ }
+ }
+
+ return e_tx(e, &m);
+}
+
+/* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_t_remote ----GSUP---> msc_a_remote --> msc_t ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+static void msc_a_remote_rx_gsup_to_msc_t(struct msc_a *msc_a, const struct osmo_gsup_message *gsup_msg)
+{
+ uint32_t event;
+ struct an_apdu an_apdu;
+
+ switch (gsup_msg->message_type) {
+ case OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST:
+ event = MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST:
+ case OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ event = MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_CLOSE:
+ case OSMO_GSUP_MSGT_E_ABORT:
+ case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
+ /* TODO: maybe some non-"normal" release with error cause? */
+ msc_a_release_cn(msc_a);
+ return;
+
+ default:
+ LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ return;
+ };
+
+ gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
+ msub_role_dispatch(msc_a->c.msub, MSC_ROLE_T, event, &an_apdu);
+ if (an_apdu.msg)
+ msgb_free(an_apdu.msg);
+}
+
+/* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+static void msc_a_remote_rx_gsup_to_msc_i(struct msc_a *msc_a, const struct osmo_gsup_message *gsup_msg)
+{
+ uint32_t event;
+ struct an_apdu an_apdu;
+
+ switch (gsup_msg->message_type) {
+ case OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ event = MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_ERROR:
+ case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_RESULT:
+ event = MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE;
+ break;
+
+ case OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_RESULT:
+ case OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_ERROR:
+ event = MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT;
+ break;
+
+ case OSMO_GSUP_MSGT_E_CLOSE:
+ case OSMO_GSUP_MSGT_E_ABORT:
+ case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
+ /* TODO: maybe some non-"normal" release with error cause? */
+ msc_a_release_cn(msc_a);
+ return;
+
+ default:
+ LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ return;
+ };
+
+ gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
+ msub_role_dispatch(msc_a->c.msub, MSC_ROLE_I, event, &an_apdu);
+ if (an_apdu.msg)
+ msgb_free(an_apdu.msg);
+}
+
+static void msc_a_remote_send_handover_failure(struct msc_a *msc_a, enum gsm0808_cause cause)
+{
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_FAILURE,
+ .handover_failure = {
+ .cause = cause,
+ },
+ };
+ struct an_apdu an_apdu = {
+ .an_proto = msc_a->c.ran->an_proto,
+ .msg = msc_role_ran_encode(msc_a->c.fi, &ran_enc_msg),
+ };
+ if (!an_apdu.msg)
+ return;
+
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T, OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR, &an_apdu);
+ msgb_free(an_apdu.msg);
+ return;
+}
+
+/* [MSC-A---------------------] [MSC-B---------------------]
+ * msc_a --> msc_{i,t}_remote ----GSUP---> msc_a_remote --> msc_{i,t} ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+static void msc_a_remote_rx_gsup(struct msc_a *msc_a, const struct osmo_gsup_message *gsup_msg)
+{
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+ struct msc_i *msc_i = msc_a_msc_i(msc_a);
+
+ /* If starting a new Handover, this subscriber *must* be new and completely unattached. Create a new msc_t role
+ * to receive below event. */
+ if (gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) {
+ if (msc_t || msc_i) {
+ LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_ERROR,
+ "Already have an MSC-T or -I role, cannot Rx %s from remote MSC\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ msc_a_remote_send_handover_failure(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ return;
+ }
+
+ msc_t = msc_t_alloc_without_ran_peer(msc_a->c.msub, msc_a->c.ran);
+ }
+
+ /* We are on a remote MSC-B. If an msub has an MSC-T role, this is the remote target of a handover, and all
+ * messages from MSC-A *must* be intended for the MSC-T role. As soon as the Handover is successful, the MSC-T
+ * role disappears and an MSC-I role appears. */
+ if (msc_t) {
+ LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "Routing to MSC-T: %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ msc_a_remote_rx_gsup_to_msc_t(msc_a, gsup_msg);
+ } else if (msc_i) {
+ LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "Routing to MSC-I: %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ msc_a_remote_rx_gsup_to_msc_i(msc_a, gsup_msg);
+ } else {
+ LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_ERROR,
+ "No MSC-T nor MSC-I role present, cannot Rx GSUP %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ }
+}
+
+static void msc_a_remote_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = msc_a_remote_priv(fi);
+ struct an_apdu *an_apdu;
+
+ switch (event) {
+
+ case MSC_REMOTE_EV_RX_GSUP:
+ /* [MSC-A---------------------] [MSC-B---------------------]
+ * msc_a --> msc_{i,t}_remote ----GSUP---> msc_a_remote --> msc_{i,t} ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+ msc_a_remote_rx_gsup(msc_a, (const struct osmo_gsup_message*)data);
+ return;
+
+ /* For all remaining cases:
+ * [MSC-A---------------------] [MSC-B---------------------]
+ * msc_a <-- msc_{i,t}_remote <---GSUP---- msc_a_remote <-- msc_{i,t} <--BSSMAP--- [BSS]
+ * you are here^
+ */
+
+ case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I,
+ OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST, an_apdu);
+ return;
+
+ case MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I,
+ OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_REQUEST, an_apdu);
+ return;
+
+ case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I,
+ OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST, an_apdu);
+ return;
+
+ case MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
+ OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_RESULT, an_apdu);
+ return;
+
+ case MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
+ OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR, an_apdu);
+ return;
+
+ case MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
+ OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST, an_apdu);
+ return;
+
+ case MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST:
+ an_apdu = data;
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
+ OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST, an_apdu);
+ return;
+
+ case MSC_A_EV_CN_CLOSE:
+ case MSC_A_EV_MO_CLOSE:
+ osmo_fsm_inst_state_chg(msc_a->c.fi, MSC_A_ST_RELEASING, 0, 0);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_a_remote_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
+}
+
+static void msc_a_remote_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_a *msc_a = msc_a_remote_priv(fi);
+ if (msc_a->c.msub->role[MSC_ROLE_I])
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I, OSMO_GSUP_MSGT_E_CLOSE, NULL);
+ if (msc_a->c.msub->role[MSC_ROLE_T])
+ msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T, OSMO_GSUP_MSGT_E_CLOSE, NULL);
+}
+
+#define S(x) (1 << (x))
+
+/* FSM events are by definition compatible with msc_a_fsm. States could be a separate enum, but so that
+ * msc_a_is_accepted() also works on remote msc_a, this FSM shares state numbers with the msc_a_fsm_states. */
+static const struct osmo_fsm_state msc_a_remote_fsm_states[] = {
+ /* Whichever MSC_A_ST would be the first for the real MSC-A implementation, a fresh FSM instance will start in
+ * state == 0 and we just need to be able to transition out of it. */
+ [0] = {
+ .name = "INIT-REMOTE",
+ .out_state_mask = 0
+ | S(MSC_A_ST_COMMUNICATING)
+ | S(MSC_A_ST_RELEASING)
+ ,
+ },
+ [MSC_A_ST_COMMUNICATING] = {
+ .name = "COMMUNICATING",
+ .action = msc_a_remote_fsm_communicating,
+ .in_event_mask = 0
+ | S(MSC_REMOTE_EV_RX_GSUP)
+ | S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST)
+ | S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE)
+ | S(MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE)
+ | S(MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST)
+ | S(MSC_A_EV_CN_CLOSE)
+ | S(MSC_A_EV_MO_CLOSE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_A_ST_RELEASING)
+ ,
+ },
+ [MSC_A_ST_RELEASING] = {
+ .name = "RELEASING",
+ .onenter = msc_a_remote_fsm_releasing_onenter,
+ },
+};
+
+static struct osmo_fsm msc_a_remote_fsm = {
+ .name = "msc_a_remote",
+ .states = msc_a_remote_fsm_states,
+ .num_states = ARRAY_SIZE(msc_a_remote_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msc_a_fsm_event_names,
+ .cleanup = msc_a_remote_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msc_a_remote_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&msc_a_remote_fsm) == 0);
+}
+
+struct msc_a *msc_a_remote_alloc(struct msub *msub, struct ran_infra *ran,
+ const uint8_t *remote_msc_name, size_t remote_msc_name_len)
+{
+ struct msc_a *msc_a;
+
+ msub_role_alloc(msub, MSC_ROLE_A, &msc_a_remote_fsm, struct msc_a, ran);
+ msc_a = msub_msc_a(msub);
+ if (!msc_a) {
+ LOG_MSUB(msub, LOGL_ERROR, "Error setting up MSC-A remote role\n");
+ return NULL;
+ }
+
+ msc_a->c.remote_to = e_link_alloc(msub_net(msub)->gcm, msc_a->c.fi, remote_msc_name, remote_msc_name_len);
+ if (!msc_a->c.remote_to) {
+ LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Failed to set up E link\n");
+ msc_a_release_cn(msc_a);
+ return NULL;
+ }
+
+ msc_a_update_id(msc_a);
+
+ /* Immediately get out of state 0. */
+ osmo_fsm_inst_state_chg(msc_a->c.fi, MSC_A_ST_COMMUNICATING, 0, 0);
+
+ return msc_a;
+}
diff --git a/src/libmsc/msc_ho.c b/src/libmsc/msc_ho.c
new file mode 100644
index 000000000..9d130c57c
--- /dev/null
+++ b/src/libmsc/msc_ho.c
@@ -0,0 +1,879 @@
+/* MSC Handover implementation */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <osmocom/msc/msc_ho.h>
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/e_link.h>
+#include <osmocom/msc/msc_i_remote.h>
+#include <osmocom/msc/msc_t_remote.h>
+#include <osmocom/msc/neighbor_ident.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/gsm_04_08.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/mncc_call.h>
+
+struct osmo_fsm msc_ho_fsm;
+
+#define MSC_A_USE_HANDOVER "Handover"
+
+static const struct osmo_tdef_state_timeout msc_ho_fsm_timeouts[32] = {
+ [MSC_HO_ST_REQUIRED] = { .keep_timer = true, .T = -3 },
+ [MSC_HO_ST_WAIT_REQUEST_ACK] = { .keep_timer = true },
+ [MSC_HO_ST_WAIT_COMPLETE] = { .T = -3 },
+};
+
+/* Transition to a state, using the T timer defined in msc_a_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define msc_ho_fsm_state_chg(msc_a, state) \
+ osmo_tdef_fsm_inst_state_chg((msc_a)->ho.fi, state, msc_ho_fsm_timeouts, (msc_a)->c.ran->tdefs, 5)
+
+static __attribute__((constructor)) void msc_ho_fsm_init()
+{
+ osmo_fsm_register(&msc_ho_fsm);
+}
+
+void msc_ho_down_required_reject(struct msc_a *msc_a, enum gsm0808_cause cause)
+{
+ struct msc_i *msc_i = msc_a_msc_i(msc_a);
+ uint32_t event;
+
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUIRED_REJECT,
+ .handover_required_reject = {
+ .cause = cause,
+ },
+ };
+
+ if (msc_i->c.remote_to)
+ event = MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR;
+ else
+ event = MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
+
+ msc_a_msg_down(msc_a, MSC_ROLE_I, event, &ran_enc_msg);
+}
+
+/* Even though this is using the 3GPP TS 48.008 definitions and naming, the intention is to be RAN implementation agnostic.
+ * For other RAN types, the 48.008 items shall be translated to their respective counterparts. */
+void msc_ho_start(struct msc_a *msc_a, const struct ran_handover_required *ho_req)
+{
+ if (msc_a->ho.fi) {
+ LOG_HO(msc_a, LOGL_ERROR, "Rx Handover Required, but Handover is still ongoing\n");
+ msc_ho_down_required_reject(msc_a, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ return;
+ }
+
+ if (!ho_req->cil.id_list_len) {
+ LOG_HO(msc_a, LOGL_ERROR, "Rx Handover Required without a Cell Identifier List\n");
+ msc_ho_down_required_reject(msc_a, GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING);
+ return;
+ }
+
+ if (msc_a_msc_t(msc_a)) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Rx Handover Required, but this subscriber still has an active MSC-T role: %s\n",
+ msc_a_msc_t(msc_a)->c.fi->id);
+ /* Protocol error because the BSS is not supposed to send another Handover Required before the previous
+ * attempt has concluded. */
+ msc_ho_down_required_reject(msc_a, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ return;
+ }
+
+ /* Paranoia: make sure we start with clean state */
+ msc_a->ho = (struct msc_ho_state){};
+
+ msc_a->ho.fi = osmo_fsm_inst_alloc_child(&msc_ho_fsm, msc_a->c.fi, MSC_A_EV_HANDOVER_END);
+ OSMO_ASSERT(msc_a->ho.fi);
+
+ msc_a->ho.fi->priv = msc_a;
+ msc_a->ho.info = *ho_req;
+ msc_a->ho.next_cil_idx = 0;
+
+ /* Start the timeout */
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_REQUIRED);
+}
+
+static void msc_ho_rtp_rollback_to_old_cell(struct msc_a *msc_a);
+
+static void msc_ho_end(struct msc_a *msc_a, bool success, enum gsm0808_cause cause)
+{
+ struct msc_i *msc_i;
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+
+ if (!success) {
+ msc_ho_rtp_rollback_to_old_cell(msc_a);
+ msc_ho_down_required_reject(msc_a, cause);
+ }
+
+ if (success) {
+ /* Any previous call forwarding to a remote MSC becomes obsolete. */
+ if (msc_a->cc.mncc_forwarding_to_remote_ran) {
+ mncc_call_release(msc_a->cc.mncc_forwarding_to_remote_ran);
+ msc_a->cc.mncc_forwarding_to_remote_ran = NULL;
+ }
+
+ /* Replace MSC-I with new MSC-T */
+ if (msc_t->c.remote_to) {
+ /* Inter-MSC Handover. */
+
+ /* The MNCC forwarding set up for inter-MSC handover, so far transitional in msc_a->ho now
+ * becomes the "officially" active MNCC forwarding for this call. */
+ msc_a->cc.mncc_forwarding_to_remote_ran = msc_a->ho.new_cell.mncc_forwarding_to_remote_ran;
+ msc_a->ho.new_cell.mncc_forwarding_to_remote_ran = NULL;
+ mncc_call_reparent(msc_a->cc.mncc_forwarding_to_remote_ran,
+ msc_a->c.fi, -1, MSC_MNCC_EV_CALL_ENDED, NULL, NULL);
+
+ /* inter-MSC link. msc_i_remote_alloc() properly "steals" the e_link from msc_t. */
+ msc_i = msc_i_remote_alloc(msc_a->c.msub, msc_t->c.ran, msc_t->c.remote_to);
+ OSMO_ASSERT(msc_t->c.remote_to == NULL);
+ } else {
+ /* local BSS */
+ msc_i = msc_i_alloc(msc_a->c.msub, msc_t->c.ran);
+ /* msc_i_set_ran_conn() properly "steals" the ran_conn from msc_t */
+ msc_i_set_ran_conn(msc_i, msc_t->ran_conn);
+ }
+ }
+
+ osmo_fsm_inst_term(msc_a->ho.fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+#define msc_ho_failed(msc_a, cause, fmt, args...) do { \
+ LOG_HO(msc_a, LOGL_ERROR, fmt, ##args); \
+ msc_ho_end(msc_a, false, cause); \
+ } while(0)
+#define msc_ho_try_next_cell(msc_a, fmt, args...) do {\
+ LOG_HO(msc_a, LOGL_ERROR, fmt, ##args); \
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_REQUIRED); \
+ } while(0)
+#define msc_ho_success(msc_a) msc_ho_end(msc_a, true, 0)
+
+enum msc_neighbor_type msc_ho_find_target_cell(struct msc_a *msc_a, const struct gsm0808_cell_id *cid,
+ const struct neighbor_ident_entry **remote_msc,
+ struct ran_peer **ran_peer_from_neighbor_ident,
+ struct ran_peer **ran_peer_from_seen_cells)
+{
+ struct gsm_network *net = msc_a_net(msc_a);
+ const struct neighbor_ident_entry *e;
+ struct sccp_ran_inst *sri;
+ struct ran_peer *rp_from_neighbor_ident = NULL;
+ struct ran_peer *rp_from_cell_id = NULL;
+ struct ran_peer *rp;
+ int i;
+
+ OSMO_ASSERT(remote_msc);
+ OSMO_ASSERT(ran_peer_from_neighbor_ident);
+ OSMO_ASSERT(ran_peer_from_seen_cells);
+
+ e = neighbor_ident_find_by_cell(&net->neighbor_ident_list, msc_a->c.ran->type, cid);
+
+ if (e && e->addr.type == MSC_NEIGHBOR_TYPE_REMOTE_MSC) {
+ *remote_msc = e;
+ return MSC_NEIGHBOR_TYPE_REMOTE_MSC;
+ }
+
+ /* It is not a remote MSC target. Figure out local RAN peers. */
+
+ if (e && e->addr.type == MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER) {
+ /* Find local RAN peer in neighbor config. If anything is wrong with that, just keep
+ * rp_from_neighbor_ident == NULL. */
+
+ struct sccp_ran_inst *sri_from_neighbor_ident = NULL;
+ struct osmo_ss7_instance *ss7 = NULL;
+
+ /* Get the sccp_ran_inst with sanity checkin. If anything is fishy, just keep
+ * sri_from_neighbor_ident == NULL and below code will notice the error. */
+ if (e->addr.ran_type < msc_ran_infra_len) {
+ sri_from_neighbor_ident = msc_ran_infra[e->addr.ran_type].sri;
+ ss7 = osmo_sccp_get_ss7(sri_from_neighbor_ident->sccp);
+ if (!ss7)
+ sri_from_neighbor_ident = NULL;
+ }
+
+ if (!sri_from_neighbor_ident) {
+ LOG_HO(msc_a, LOGL_ERROR, "Cannot handover to RAN type %s\n", osmo_rat_type_name(e->addr.ran_type));
+ } else {
+ /* Interpret the point-code string placed in the neighbors config. */
+ int pc = osmo_ss7_pointcode_parse(ss7, e->addr.local_ran_peer_pc_str);
+
+ if (pc < 0) {
+ LOG_HO(msc_a, LOGL_ERROR, "Invalid point code string: %s\n",
+ osmo_quote_str(e->addr.local_ran_peer_pc_str, -1));
+ } else {
+ struct osmo_sccp_addr addr = {};
+ osmo_sccp_make_addr_pc_ssn(&addr, pc, sri_from_neighbor_ident->ran->ssn);
+ rp_from_neighbor_ident = ran_peer_find_by_addr(sri_from_neighbor_ident, &addr);
+ }
+ }
+
+ if (!rp_from_neighbor_ident) {
+ LOG_HO(msc_a, LOGL_ERROR, "Target RAN peer from neighbor config is not connected:"
+ " Cell ID %s resolves to target address %s\n",
+ gsm0808_cell_id_name(cid), e->addr.local_ran_peer_pc_str);
+ } else if (rp_from_neighbor_ident->fi->state != RAN_PEER_ST_READY) {
+ LOG_HO(msc_a, LOGL_ERROR, "Target RAN peer in invalid state: %s (%s)\n",
+ osmo_fsm_inst_state_name(rp_from_neighbor_ident->fi),
+ rp_from_neighbor_ident->fi->id);
+ rp_from_neighbor_ident = NULL;
+ }
+ }
+
+ /* Figure out actually connected RAN peers for this cell ID.
+ * If no cell has been found yet at all, this might determine a Handover target,
+ * otherwise this is for sanity checking. If none is found, just keep rp_from_cell_id == NULL. */
+
+ /* Iterate all connected RAN peers. Possibly, more than one RAN peer has advertised a match for this Cell ID.
+ * For example, if the handover target is identified as LAC=23 but there are multiple cells with distinct CIs
+ * serving in LAC=23, we have an ambiguity. It's up to the user to configure correctly, help with logging. */
+ for (i = 0; i < msc_ran_infra_len; i++) {
+ sri = msc_ran_infra[i].sri;
+ if (!sri)
+ continue;
+
+ rp = ran_peer_find_by_cell_id(sri, cid, true);
+ if (rp && rp->fi && rp->fi->state == RAN_PEER_ST_READY) {
+ if (rp_from_cell_id) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Ambiguous match for cell ID %s: more than one RAN type is serving this cell"
+ " ID: %s and %s\n",
+ gsm0808_cell_id_name(cid),
+ rp_from_cell_id->fi->id,
+ rp->fi->id);
+ /* But logging is all we're going to do about it. */
+ }
+
+ /* Use the first found RAN peer, but if multiple matches are found, favor the one that matches
+ * the current RAN type. */
+ if (!rp_from_cell_id || rp->sri == msc_a->c.ran->sri)
+ rp_from_cell_id = rp;
+ }
+ }
+
+ /* Did we find mismatching targets from neighbor config and from connected cells? */
+ if (rp_from_neighbor_ident && rp_from_cell_id
+ && rp_from_neighbor_ident != rp_from_cell_id) {
+ LOG_HO(msc_a, LOGL_ERROR, "Ambiguous match for cell ID %s:"
+ " neighbor config points at %s; a matching cell is also served by connected RAN peer %s\n",
+ gsm0808_cell_id_name(cid), rp_from_neighbor_ident->fi->id, rp_from_cell_id->fi->id);
+ /* But logging is all we're going to do about it. */
+ }
+
+ if (rp_from_neighbor_ident && rp_from_neighbor_ident->sri != msc_a->c.ran->sri) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Neighbor config indicates inter-RAT Handover, which is not implemented. Ignoring target %s\n",
+ rp_from_neighbor_ident->fi->id);
+ rp_from_neighbor_ident = NULL;
+ }
+
+ if (rp_from_cell_id && rp_from_cell_id->sri != msc_a->c.ran->sri) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Target RAN peer indicates inter-RAT Handover, which is not implemented. Ignoring target %s\n",
+ rp_from_cell_id->fi->id);
+ rp_from_cell_id = NULL;
+ }
+
+ *ran_peer_from_neighbor_ident = rp_from_neighbor_ident;
+ *ran_peer_from_seen_cells = rp_from_cell_id;
+
+ return rp_from_neighbor_ident || rp_from_cell_id ? MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER : MSC_NEIGHBOR_TYPE_NONE;
+}
+
+static bool msc_ho_find_next_target_cell(struct msc_a *msc_a)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct ran_handover_required *info = &msc_a->ho.info;
+ struct gsm0808_cell_id *cid = &msc_a->ho.new_cell.cid;
+ const struct neighbor_ident_entry *e;
+ struct ran_peer *rp_from_neighbor_ident = NULL;
+ struct ran_peer *rp_from_cell_id = NULL;
+ struct ran_peer *rp;
+
+ unsigned int cil_idx = msc_a->ho.next_cil_idx;
+ msc_a->ho.next_cil_idx++;
+
+ msc_a->ho.new_cell.type = MSC_NEIGHBOR_TYPE_NONE;
+
+ if (cil_idx >= info->cil.id_list_len)
+ return false;
+
+ *cid = (struct gsm0808_cell_id){
+ .id_discr = info->cil.id_discr,
+ .id = info->cil.id_list[cil_idx],
+ };
+
+ msc_a->ho.new_cell.cgi = (struct osmo_cell_global_id){
+ .lai = vsub->cgi.lai,
+ };
+ gsm0808_cell_id_to_cgi(&msc_a->ho.new_cell.cgi, cid);
+
+ switch (msc_ho_find_target_cell(msc_a, cid, &e, &rp_from_neighbor_ident, &rp_from_cell_id)) {
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ OSMO_ASSERT(e);
+ msc_a->ho.new_cell.ran_type = e->addr.ran_type;
+ msc_a->ho.new_cell.type = MSC_NEIGHBOR_TYPE_REMOTE_MSC;
+ msc_a->ho.new_cell.msc_ipa_name = e->addr.remote_msc_ipa_name.buf;
+ return true;
+
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ rp = rp_from_neighbor_ident ? : rp_from_cell_id;
+ OSMO_ASSERT(rp);
+ msc_a->ho.new_cell.type = MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER;
+ msc_a->ho.new_cell.ran_peer = rp;
+ return true;
+
+ default:
+ break;
+ }
+
+ LOG_HO(msc_a, LOGL_DEBUG, "Cannot find target peer for cell ID %s\n", gsm0808_cell_id_name(cid));
+ /* Try the next cell id, if any. */
+ return msc_ho_find_next_target_cell(msc_a);
+}
+
+static void msc_ho_fsm_required_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ if (!msc_ho_find_next_target_cell(msc_a)) {
+ int tried = msc_a->ho.next_cil_idx - 1;
+ msc_ho_failed(msc_a, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Attempted Handover to %u cells without success\n", tried);
+ return;
+ }
+
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_WAIT_REQUEST_ACK);
+}
+
+static void msc_ho_send_handover_request(struct msc_a *msc_a)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct gsm0808_channel_type channel_type;
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUEST,
+ .handover_request = {
+ .imsi = vsub->imsi,
+ .classmark = &vsub->classmark,
+ .geran = {
+ .chosen_encryption = &msc_a->geran_encr,
+ .a5_encryption_mask = net->a5_encryption_mask,
+ },
+ .bssap_cause = GSM0808_CAUSE_BETTER_CELL,
+ .current_channel_type_1_present = msc_a->ho.info.current_channel_type_1_present,
+ .current_channel_type_1 = msc_a->ho.info.current_channel_type_1,
+ .speech_version_used = msc_a->ho.info.speech_version_used,
+ .old_bss_to_new_bss_info_raw = msc_a->ho.info.old_bss_to_new_bss_info_raw,
+ .old_bss_to_new_bss_info_raw_len = msc_a->ho.info.old_bss_to_new_bss_info_raw_len,
+
+ /* Don't send AoIP Transport Layer Address for inter-MSC Handover */
+ .rtp_ran_local = (msc_a->ho.new_cell.type == MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER)
+ ? call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_RAN) : NULL,
+ },
+ };
+
+ if (msc_a->cc.active_trans) {
+ if (mncc_bearer_cap_to_channel_type(&channel_type, &msc_a->cc.active_trans->bearer_cap)) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Failed to encode Bearer Cap to Channel Type\n");
+ return;
+ }
+ ran_enc_msg.handover_request.geran.channel_type = &channel_type;
+ }
+
+ gsm0808_cell_id_from_cgi(&ran_enc_msg.handover_request.cell_id_serving, CELL_IDENT_WHOLE_GLOBAL, &vsub->cgi);
+ ran_enc_msg.handover_request.cell_id_target = msc_a->ho.new_cell.cid;
+
+ if (msc_a_msg_down(msc_a, MSC_ROLE_T, MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST, &ran_enc_msg))
+ msc_ho_try_next_cell(msc_a, "Failed to send Handover Request message\n");
+}
+
+static void msc_ho_fsm_wait_request_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct msc_i *msc_i = msc_a_msc_i(msc_a);
+ struct msc_t *msc_t;
+ struct ran_peer *rp;
+ const char *ipa_name;
+
+ msc_t = msc_a_msc_t(msc_a);
+ if (msc_t) {
+ /* All the other code should prevent this from happening, ever. */
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, there still is an active MSC-T role: %s\n",
+ msc_t->c.fi->id);
+ return;
+ }
+
+ if (!msc_i) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, there is no MSC-I role\n");
+ return;
+ }
+
+ if (!msc_i->c.remote_to
+ && !(msc_i->ran_conn && msc_i->ran_conn->ran_peer)) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, MSC-I role has no connection\n");
+ return;
+ }
+
+ switch (msc_a->ho.new_cell.type) {
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ rp = msc_a->ho.new_cell.ran_peer;
+ OSMO_ASSERT(rp && rp->fi);
+
+ if (msc_i->c.remote_to) {
+ LOG_HO(msc_a, LOGL_INFO,
+ "Starting inter-MSC Subsequent Handover from remote MSC %s to local %s\n",
+ msc_i->c.remote_to->remote_name, rp->fi->id);
+ msc_a->ho.subsequent_ho = true;
+ } else {
+ LOG_HO(msc_a, LOGL_INFO, "Starting inter-BSC Handover from %s to %s\n",
+ msc_i->ran_conn->ran_peer->fi->id, rp->fi->id);
+ }
+
+ msc_t_alloc(msc_a->c.msub, rp);
+ msc_ho_send_handover_request(msc_a);
+ return;
+
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ ipa_name = msc_a->ho.new_cell.msc_ipa_name;
+ OSMO_ASSERT(ipa_name);
+
+ if (msc_i->c.remote_to) {
+ LOG_HO(msc_a, LOGL_INFO,
+ "Starting inter-MSC Subsequent Handover from remote MSC %s to remote MSC at %s\n",
+ msc_i->c.remote_to->remote_name, osmo_quote_str(ipa_name, -1));
+ msc_a->ho.subsequent_ho = true;
+ } else {
+ LOG_HO(msc_a, LOGL_INFO, "Starting inter-MSC Handover from local %s to remote MSC at %s\n",
+ msc_i->ran_conn->ran_peer->fi->id,
+ osmo_quote_str(ipa_name, -1));
+ }
+
+ msc_t_remote_alloc(msc_a->c.msub, msc_a->c.ran, (const uint8_t*)ipa_name, strlen(ipa_name));
+ msc_ho_send_handover_request(msc_a);
+ return;
+
+ default:
+ msc_ho_try_next_cell(msc_a, "unknown Handover target type %d\n", msc_a->ho.new_cell.type);
+ return;
+ }
+
+ msc_t = msc_a_msc_t(msc_a);
+ if (!msc_t) {
+ /* There should definitely be one now. */
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, failed to set up a target MSC-T\n");
+ return;
+ }
+}
+
+static void msc_ho_rx_request_ack(struct msc_a *msc_a, struct msc_a_ran_dec_data *hra);
+
+static void msc_ho_fsm_wait_request_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ switch (event) {
+
+ case MSC_HO_EV_RX_REQUEST_ACK:
+ msc_ho_rx_request_ack(msc_a, (struct msc_a_ran_dec_data*)data);
+ return;
+
+ case MSC_HO_EV_RX_FAILURE:
+ msc_ho_failed(msc_a, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Received Handover Failure message\n");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a);
+
+void msc_ho_mncc_forward_cb(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg, void *data)
+{
+ struct msc_a *msc_a = data;
+ switch (mncc_msg->msg_type) {
+ case MNCC_RTP_CONNECT:
+ msc_a->ho.rtp_switched_to_new_cell = true;
+ return;
+ default:
+ return;
+ }
+}
+
+/* Initiate call forwarding via MNCC: call the Handover Number that the other MSC assigned. */
+static int msc_ho_start_inter_msc_call_forwarding(struct msc_a *msc_a, struct msc_t *msc_t,
+ const struct msc_a_ran_dec_data *hra)
+{
+ const struct osmo_gsup_message *e_info = hra->an_apdu->e_info;
+ struct gsm_mncc outgoing_call_req = {};
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct rtp_stream *rtp_to_ran = cl ? cl->rtp[RTP_TO_RAN] : NULL;
+ struct mncc_call *mncc_call;
+
+ if (!e_info || !e_info->msisdn_enc || !e_info->msisdn_enc_len) {
+ msc_ho_try_next_cell(msc_a,
+ "No Handover Number in Handover Request Acknowledge from remote MSC\n");
+ return -EINVAL;
+ }
+
+ /* Backup old cell's RTP IP:port and codec data */
+ msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote;
+ msc_a->ho.old_cell.codec = rtp_to_ran->codec;
+
+ /* Blindly taken over from an MNCC trace of existing code: send an all-zero CCCAP: */
+ outgoing_call_req.fields |= MNCC_F_CCCAP;
+
+ /* Called number */
+ outgoing_call_req.fields |= MNCC_F_CALLED;
+ outgoing_call_req.called.plan = 1; /* Empirical magic number. There seem to be no enum or defines for this.
+ * The only other place setting this apparently is gsm48_decode_called(). */
+ if (gsm48_decode_bcd_number2(outgoing_call_req.called.number, sizeof(outgoing_call_req.called.number),
+ e_info->msisdn_enc, e_info->msisdn_enc_len, 0)) {
+ msc_ho_try_next_cell(msc_a,
+ "Failed to decode Handover Number in Handover Request Acknowledge"
+ " from remote MSC\n");
+ return -EINVAL;
+ }
+
+ if (msc_a->cc.active_trans) {
+ outgoing_call_req.fields |= MNCC_F_BEARER_CAP;
+ outgoing_call_req.bearer_cap = msc_a->cc.active_trans->bearer_cap;
+ }
+
+ mncc_call = mncc_call_alloc(msc_a_vsub(msc_a),
+ msc_a->ho.fi,
+ MSC_HO_EV_MNCC_FORWARDING_COMPLETE,
+ MSC_HO_EV_MNCC_FORWARDING_FAILED,
+ msc_ho_mncc_forward_cb, msc_a);
+
+ mncc_call_set_rtp_stream(mncc_call, rtp_to_ran);
+ msc_a->ho.new_cell.mncc_forwarding_to_remote_ran = mncc_call;
+ return mncc_call_outgoing_start(mncc_call, &outgoing_call_req);
+}
+
+static void msc_ho_rx_request_ack(struct msc_a *msc_a, struct msc_a_ran_dec_data *hra)
+{
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+ struct ran_msg ran_enc_msg;
+
+ OSMO_ASSERT(hra->ran_dec);
+ OSMO_ASSERT(hra->an_apdu);
+
+ if (!msc_t) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "MSC-T role missing\n");
+ return;
+ }
+
+ if (!hra->ran_dec->handover_request_ack.rr_ho_command
+ || !hra->ran_dec->handover_request_ack.rr_ho_command_len) {
+ msc_ho_try_next_cell(msc_a, "Missing mandatory IE in Handover Request Acknowledge:"
+ " L3 Info (RR Handover Command)\n");
+ return;
+ }
+
+ if (!hra->ran_dec->handover_request_ack.chosen_channel_present) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No 'Chosen Channel' IE in Handover Request Ack\n");
+ msc_t->geran.chosen_channel = 0;
+ } else
+ msc_t->geran.chosen_channel = hra->ran_dec->handover_request_ack.chosen_channel;
+
+ if (!hra->ran_dec->handover_request_ack.chosen_encr_alg) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No 'Chosen Encryption Algorithm' IE in Handover Request Ack\n");
+ msc_t->geran.chosen_encr_alg = 0;
+ } else {
+ msc_t->geran.chosen_encr_alg = hra->ran_dec->handover_request_ack.chosen_encr_alg;
+ if (msc_t->geran.chosen_encr_alg < 1 || msc_t->geran.chosen_encr_alg > 8) {
+ msc_ho_try_next_cell(msc_a, "Handover Request Ack: Invalid 'Chosen Encryption Algorithm': %u\n",
+ msc_t->geran.chosen_encr_alg);
+ return;
+ }
+ }
+
+ msc_t->geran.chosen_speech_version = hra->ran_dec->handover_request_ack.chosen_speech_version;
+ if (!msc_t->geran.chosen_speech_version)
+ LOG_HO(msc_a, LOGL_DEBUG, "No 'Chosen Speech Version' IE in Handover Request Ack\n");
+
+ /* Inter-MSC call forwarding? */
+ if (msc_a->ho.new_cell.type == MSC_NEIGHBOR_TYPE_REMOTE_MSC) {
+ if (msc_ho_start_inter_msc_call_forwarding(msc_a, msc_t, hra))
+ return;
+ }
+
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_WAIT_COMPLETE);
+
+ /* Forward the RR Handover Command composed by the new RAN peer down to the old RAN peer */
+ ran_enc_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_HANDOVER_COMMAND,
+ .handover_command = {
+ .rr_ho_command = hra->ran_dec->handover_request_ack.rr_ho_command,
+ .rr_ho_command_len = hra->ran_dec->handover_request_ack.rr_ho_command_len,
+ },
+ };
+
+ if (msc_a_msg_down(msc_a, MSC_ROLE_I,
+ msc_a->ho.subsequent_ho ? MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT
+ : MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST,
+ &ran_enc_msg)) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "Failed to send Handover Command\n");
+ return;
+ }
+
+ msc_a->ho.new_cell.ran_remote_rtp = hra->ran_dec->handover_request_ack.remote_rtp;
+ if (osmo_sockaddr_str_is_set(&msc_a->ho.new_cell.ran_remote_rtp)) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Request Ack contains cell's RTP address " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.new_cell.ran_remote_rtp));
+ }
+
+ msc_a->ho.new_cell.codec_present = hra->ran_dec->handover_request_ack.codec_present;
+ msc_a->ho.new_cell.codec = hra->ran_dec->handover_request_ack.codec;
+ if (hra->ran_dec->handover_request_ack.codec_present) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Request Ack contains codec %s\n",
+ osmo_mgcpc_codec_name(msc_a->ho.new_cell.codec));
+ }
+}
+
+static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a)
+{
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct rtp_stream *rtp_to_ran = cl ? cl->rtp[RTP_TO_RAN] : NULL;
+
+ if (!rtp_to_ran) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No RTP stream, nothing to switch\n");
+ return;
+ }
+
+ if (!osmo_sockaddr_str_is_set(&msc_a->ho.new_cell.ran_remote_rtp)) {
+ LOG_HO(msc_a, LOGL_DEBUG, "New cell's RTP IP:port not yet known, not switching RTP stream\n");
+ return;
+ }
+
+ if (msc_a->ho.rtp_switched_to_new_cell) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Already switched RTP to new cell\n");
+ return;
+ }
+ msc_a->ho.rtp_switched_to_new_cell = true;
+
+ /* Backup old cell's RTP IP:port and codec data */
+ msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote;
+ msc_a->ho.old_cell.codec = rtp_to_ran->codec;
+
+ LOG_HO(msc_a, LOGL_DEBUG, "Switching RTP stream to new cell: from " OSMO_SOCKADDR_STR_FMT " to " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.old_cell.ran_remote_rtp),
+ OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.new_cell.ran_remote_rtp));
+
+ /* If a previous forwarding to a remote MSC is still active, this now becomes no longer responsible for the RTP
+ * stream. */
+ if (msc_a->cc.mncc_forwarding_to_remote_ran) {
+ if (msc_a->cc.mncc_forwarding_to_remote_ran->rtps != rtp_to_ran) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Unexpected state: previous MNCC forwarding not using RTP-to-RAN stream\n");
+ /* That would be weird, but carry on anyway... */
+ }
+ mncc_call_detach_rtp_stream(msc_a->cc.mncc_forwarding_to_remote_ran);
+ }
+
+ /* Switch over to the new peer */
+ rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.new_cell.ran_remote_rtp);
+ if (msc_a->ho.new_cell.codec_present)
+ rtp_stream_set_codec(rtp_to_ran, msc_a->ho.new_cell.codec);
+ else
+ LOG_HO(msc_a, LOGL_ERROR, "No codec is set\n");
+ rtp_stream_commit(rtp_to_ran);
+}
+
+static void msc_ho_rtp_rollback_to_old_cell(struct msc_a *msc_a)
+{
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct rtp_stream *rtp_to_ran = cl ? cl->rtp[RTP_TO_RAN] : NULL;
+
+ if (!msc_a->ho.rtp_switched_to_new_cell) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Not switched RTP to new cell yet, no need to roll back\n");
+ return;
+ }
+
+ if (!rtp_to_ran) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No RTP stream, nothing to switch\n");
+ return;
+ }
+
+ if (!osmo_sockaddr_str_is_set(&msc_a->ho.old_cell.ran_remote_rtp)) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Have no RTP IP:port for the old cell, not switching back to\n");
+ return;
+ }
+
+ /* The new call forwarding to a remote MSC is no longer needed because the handover failed */
+ if (msc_a->ho.new_cell.mncc_forwarding_to_remote_ran)
+ mncc_call_detach_rtp_stream(msc_a->ho.new_cell.mncc_forwarding_to_remote_ran);
+
+ /* If before this handover, there was a call forwarding to a remote MSC in place, this now goes back into
+ * responsibility. */
+ if (msc_a->cc.mncc_forwarding_to_remote_ran)
+ mncc_call_set_rtp_stream(msc_a->cc.mncc_forwarding_to_remote_ran, rtp_to_ran);
+
+ msc_a->ho.rtp_switched_to_new_cell = false;
+ msc_a->ho.ready_to_switch_rtp = false;
+ LOG_HO(msc_a, LOGL_NOTICE, "Switching RTP back to old cell\n");
+
+ /* Switch back to the old cell */
+ rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.old_cell.ran_remote_rtp);
+ rtp_stream_set_codec(rtp_to_ran, msc_a->ho.old_cell.codec);
+ rtp_stream_commit(rtp_to_ran);
+}
+
+static void msc_ho_send_handover_succeeded(struct msc_a *msc_a)
+{
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_SUCCEEDED,
+ };
+
+ if (msc_a_msg_down(msc_a, MSC_ROLE_I, MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST, &ran_enc_msg))
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "Failed to send Handover Succeeded message\n");
+}
+
+static void msc_ho_fsm_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ switch (event) {
+
+ case MSC_HO_EV_RX_DETECT:
+ msc_a->ho.ready_to_switch_rtp = true;
+ /* For inter-MSC, the mncc_fsm switches the rtp_stream upon MNCC_RTP_CONNECT.
+ * For inter-BSC, need to switch here to the address obtained from Handover Request Ack. */
+ if (msc_a->ho.new_cell.type == MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER)
+ msc_ho_rtp_switch_to_new_cell(msc_a);
+ msc_ho_send_handover_succeeded(msc_a);
+ return;
+
+ case MSC_HO_EV_RX_COMPLETE:
+ msc_ho_success(msc_a);
+ return;
+
+ case MSC_HO_EV_RX_FAILURE:
+ msc_ho_failed(msc_a, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Received Handover Failure message\n");
+ return;
+
+ case MSC_HO_EV_MNCC_FORWARDING_FAILED:
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "MNCC Forwarding failed\n");
+ return;
+
+ case MSC_HO_EV_MNCC_FORWARDING_COMPLETE:
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_ho_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+
+ /* paranoia */
+ if (msc_a->ho.fi != fi)
+ return;
+
+ /* Completely clear all handover state */
+ msc_a->ho = (struct msc_ho_state){};
+
+ if (msc_t)
+ msc_t_clear(msc_t);
+}
+
+static int msc_ho_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ return 1;
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_ho_fsm_states[] = {
+ [MSC_HO_ST_REQUIRED] = {
+ .name = OSMO_STRINGIFY(MSC_HO_ST_REQUIRED),
+ .out_state_mask = 0
+ | S(MSC_HO_ST_REQUIRED)
+ | S(MSC_HO_ST_WAIT_REQUEST_ACK)
+ ,
+ .onenter = msc_ho_fsm_required_onenter,
+ },
+ [MSC_HO_ST_WAIT_REQUEST_ACK] = {
+ .name = OSMO_STRINGIFY(MSC_HO_ST_WAIT_REQUEST_ACK),
+ .in_event_mask = 0
+ | S(MSC_HO_EV_RX_REQUEST_ACK)
+ | S(MSC_HO_EV_RX_FAILURE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_HO_ST_REQUIRED)
+ | S(MSC_HO_ST_WAIT_COMPLETE)
+ ,
+ .onenter = msc_ho_fsm_wait_request_ack_onenter,
+ .action = msc_ho_fsm_wait_request_ack,
+ },
+ [MSC_HO_ST_WAIT_COMPLETE] = {
+ .name = OSMO_STRINGIFY(MSC_HO_ST_WAIT_COMPLETE),
+ .in_event_mask = 0
+ | S(MSC_HO_EV_RX_DETECT)
+ | S(MSC_HO_EV_RX_COMPLETE)
+ | S(MSC_HO_EV_RX_FAILURE)
+ | S(MSC_HO_EV_MNCC_FORWARDING_COMPLETE)
+ | S(MSC_HO_EV_MNCC_FORWARDING_FAILED)
+ ,
+ .action = msc_ho_fsm_wait_complete,
+ },
+};
+
+static const struct value_string msc_ho_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_REQUEST_ACK),
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_DETECT),
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_COMPLETE),
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_FAILURE),
+ {}
+};
+
+struct osmo_fsm msc_ho_fsm = {
+ .name = "handover",
+ .states = msc_ho_fsm_states,
+ .num_states = ARRAY_SIZE(msc_ho_fsm_states),
+ .log_subsys = DHO,
+ .event_names = msc_ho_fsm_event_names,
+ .timer_cb = msc_ho_fsm_timer_cb,
+ .cleanup = msc_ho_fsm_cleanup,
+};
diff --git a/src/libmsc/msc_i.c b/src/libmsc/msc_i.c
new file mode 100644
index 000000000..6badba668
--- /dev/null
+++ b/src/libmsc/msc_i.c
@@ -0,0 +1,383 @@
+/* Code to manage a subscriber's MSC-I role */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/msc/gsm_data.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/ran_conn.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/mncc_call.h>
+
+static struct osmo_fsm msc_i_fsm;
+
+struct ran_infra *msc_i_ran(struct msc_i *msc_i)
+{
+ OSMO_ASSERT(msc_i
+ && msc_i->ran_conn
+ && msc_i->ran_conn->ran_peer
+ && msc_i->ran_conn->ran_peer->sri
+ && msc_i->ran_conn->ran_peer->sri->ran);
+ return msc_i->ran_conn->ran_peer->sri->ran;
+}
+
+static int msc_i_ran_enc(struct msc_i *msc_i, const struct ran_msg *ran_enc_msg)
+{
+ struct msgb *l3 = msc_role_ran_encode(msc_i->c.fi, ran_enc_msg);
+ if (!l3)
+ return -EIO;
+ return msc_i_down_l2(msc_i, l3);
+}
+
+struct msc_i *msc_i_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &msc_i_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+int msc_i_ready_decode_cb(struct osmo_fsm_inst *msc_i_fi, void *data, const struct ran_msg *msg)
+{
+ struct msc_i *msc_i = msc_i_priv(msc_i_fi);
+ struct msc_a *msc_a = msub_msc_a(msc_i->c.msub);
+ const struct an_apdu *an_apdu = data;
+ uint32_t event;
+
+ event = MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST;
+
+ switch (msg->msg_type) {
+ case RAN_MSG_HANDOVER_REQUIRED:
+ if (msc_a->c.remote_to) {
+ /* We're already a remote MSC-B, this hence must be a "subsequent" handover.
+ * There is not much difference really from dispatching a Process Access Signalling Request,
+ * only that 3GPP TS 29.010 specifies the different message type. */
+ event = MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return msub_role_dispatch(msc_i->c.msub, MSC_ROLE_A, event, an_apdu);
+}
+
+void msc_i_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_i *msc_i = msc_i_priv(fi);
+ struct msc_a *msc_a = msub_msc_a(msc_i->c.msub);
+ struct an_apdu *an_apdu;
+
+ if (!msc_a) {
+ LOG_MSC_I(msc_i, LOGL_ERROR, "No MSC-A role\n");
+ return;
+ }
+
+ switch (event) {
+
+ case MSC_EV_FROM_RAN_COMPLETE_LAYER_3:
+ an_apdu = data;
+ msub_role_dispatch(msc_i->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_I_COMPLETE_LAYER_3, an_apdu);
+ break;
+
+ case MSC_EV_FROM_RAN_UP_L2:
+ an_apdu = data;
+ /* To send the correct event types like MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST and hence
+ * reflect the correct GSUP message type on an inter-MSC link, need to decode the message here. */
+ msc_role_ran_decode(msc_i->c.fi, an_apdu, msc_i_ready_decode_cb, an_apdu);
+ break;
+
+ case MSC_EV_FROM_RAN_CONN_RELEASED:
+ msc_i_cleared(msc_i);
+ break;
+
+ case MSC_EV_CALL_LEG_TERM:
+ msc_i->inter_msc.call_leg = NULL;
+ if (msc_i->inter_msc.mncc_forwarding_to_remote_cn)
+ msc_i->inter_msc.mncc_forwarding_to_remote_cn->rtps = NULL;
+ break;
+
+ case MSC_MNCC_EV_CALL_ENDED:
+ msc_i->inter_msc.mncc_forwarding_to_remote_cn = NULL;
+ break;
+
+ case MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ case MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT:
+ case MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR:
+ an_apdu = data;
+ if (an_apdu->an_proto != msc_i_ran(msc_i)->an_proto) {
+ LOG_MSC_I(msc_i, LOGL_ERROR, "Mismatching AN-APDU proto: %s -- Dropping message\n",
+ an_proto_name(an_apdu->an_proto));
+ msgb_free(an_apdu->msg);
+ an_apdu->msg = NULL;
+ return;
+ }
+ msc_i_down_l2(msc_i, an_apdu->msg);
+ break;
+
+ case MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE:
+ msc_i_clear(msc_i);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+void msc_i_fsm_clearing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_i *msc_i = msc_i_priv(fi);
+ struct ran_msg msg = {
+ .msg_type = RAN_MSG_CLEAR_COMMAND,
+ /* Concerning CSFB (Circuit-Switched FallBack from LTE), for a final Clear Command that might indicate
+ * CSFB, the MSC-A has to send the Clear Command. This Clear Command is about detaching an MSC-I when a
+ * new MSC-I has shown up after an inter-BSC or inter-MSC Handover succeeded. So never CSFB here. */
+ };
+ msc_i_ran_enc(msc_i, &msg);
+}
+
+int msc_i_clearing_decode_cb(struct osmo_fsm_inst *msc_i_fi, void *data, const struct ran_msg *msg)
+{
+ struct msc_i *msc_i = msc_i_fi->priv;
+
+ switch (msg->msg_type) {
+
+ case RAN_MSG_CLEAR_COMPLETE:
+ switch (msc_i->c.fi->state) {
+ case MSC_I_ST_CLEARING:
+ osmo_fsm_inst_state_chg(msc_i->c.fi, MSC_I_ST_CLEARED, 0, 0);
+ return 0;
+ case MSC_I_ST_CLEARED:
+ return 0;
+ default:
+ LOG_MSC_I(msc_i, LOGL_ERROR, "Received Clear Complete, but did not send Clear Command\n");
+ {
+ struct msc_a *msc_a = msub_msc_a(msc_i->c.msub);
+ if (msc_a)
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_MO_CLOSE, NULL);
+ }
+ return 0;
+ }
+
+ default:
+ LOG_MSC_I(msc_i, LOGL_ERROR, "Message not handled: %s\n", ran_msg_type_name(msg->msg_type));
+ return -ENOTSUP;
+ }
+}
+
+void msc_i_fsm_clearing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_i *msc_i = msc_i_priv(fi);
+ struct an_apdu *an_apdu;
+
+ /* We expect a Clear Complete and nothing else. */
+ switch (event) {
+ case MSC_EV_FROM_RAN_UP_L2:
+ an_apdu = data;
+ msc_role_ran_decode(msc_i->c.fi, an_apdu, msc_i_clearing_decode_cb, NULL);
+ return;
+
+ case MSC_EV_FROM_RAN_CONN_RELEASED:
+ msc_i_cleared(msc_i);
+ return;
+
+ case MSC_EV_CALL_LEG_TERM:
+ msc_i->inter_msc.call_leg = NULL;
+ if (msc_i->inter_msc.mncc_forwarding_to_remote_cn)
+ msc_i->inter_msc.mncc_forwarding_to_remote_cn->rtps = NULL;
+ break;
+
+ case MSC_MNCC_EV_CALL_ENDED:
+ msc_i->inter_msc.mncc_forwarding_to_remote_cn = NULL;
+ break;
+ }
+}
+
+void msc_i_fsm_cleared_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
+}
+
+void msc_i_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_i *msc_i = msc_i_priv(fi);
+
+ call_leg_release(msc_i->inter_msc.call_leg);
+ mncc_call_release(msc_i->inter_msc.mncc_forwarding_to_remote_cn);
+
+ if (msc_i->ran_conn)
+ ran_conn_msc_role_gone(msc_i->ran_conn, msc_i->c.fi);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_i_fsm_states[] = {
+ [MSC_I_ST_READY] = {
+ .name = "READY",
+ .action = msc_i_fsm_ready,
+ .in_event_mask = 0
+ | S(MSC_EV_FROM_RAN_COMPLETE_LAYER_3)
+ | S(MSC_EV_FROM_RAN_UP_L2)
+ | S(MSC_EV_FROM_RAN_CONN_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ | S(MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT)
+ | S(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR)
+ | S(MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_I_ST_CLEARING)
+ | S(MSC_I_ST_CLEARED)
+ ,
+ },
+ [MSC_I_ST_CLEARING] = {
+ .name = "CLEARING",
+ .onenter = msc_i_fsm_clearing_onenter,
+ .action = msc_i_fsm_clearing,
+ .in_event_mask = 0
+ | S(MSC_EV_FROM_RAN_UP_L2)
+ | S(MSC_EV_FROM_RAN_CONN_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ ,
+ .out_state_mask = 0
+ | S(MSC_I_ST_CLEARED)
+ ,
+ },
+ [MSC_I_ST_CLEARED] = {
+ .name = "CLEARED",
+ .onenter = msc_i_fsm_cleared_onenter,
+ },
+};
+
+const struct value_string msc_i_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MSC_REMOTE_EV_RX_GSUP),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_COMPLETE),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_RELEASED),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_TERM),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_NEED_LOCAL_RTP),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_PROCEEDING),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_COMPLETE),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_ENDED),
+
+ OSMO_VALUE_STRING(MSC_EV_FROM_RAN_COMPLETE_LAYER_3),
+ OSMO_VALUE_STRING(MSC_EV_FROM_RAN_UP_L2),
+ OSMO_VALUE_STRING(MSC_EV_FROM_RAN_CONN_RELEASED),
+
+ OSMO_VALUE_STRING(MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST),
+ OSMO_VALUE_STRING(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT),
+ OSMO_VALUE_STRING(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR),
+ OSMO_VALUE_STRING(MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE),
+ {}
+};
+
+static struct osmo_fsm msc_i_fsm = {
+ .name = "msc_i",
+ .states = msc_i_fsm_states,
+ .num_states = ARRAY_SIZE(msc_i_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msc_i_fsm_event_names,
+ .cleanup = msc_i_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msc_i_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&msc_i_fsm) == 0);
+}
+
+/* Send connection-oriented L3 message to RAN peer (MSC->[BSC|RNC]) */
+int msc_i_down_l2(struct msc_i *msc_i, struct msgb *l3)
+{
+ int rc;
+ if (!msc_i->ran_conn) {
+ LOG_MSC_I(msc_i, LOGL_ERROR, "Cannot Tx L2 message: no RAN conn\n");
+ return -EIO;
+ }
+
+ rc = ran_conn_down_l2_co(msc_i->ran_conn, l3, false);
+ if (rc)
+ LOG_MSC_I(msc_i, LOGL_ERROR, "Failed to transfer message down to subscriber (rc=%d)\n", rc);
+ return rc;
+}
+
+struct gsm_network *msc_i_net(const struct msc_i *msc_i)
+{
+ return msub_net(msc_i->c.msub);
+}
+
+struct vlr_subscr *msc_i_vsub(const struct msc_i *msc_i)
+{
+ return msub_vsub(msc_i->c.msub);
+}
+
+struct msc_i *msc_i_alloc(struct msub *msub, struct ran_infra *ran)
+{
+ return msub_role_alloc(msub, MSC_ROLE_I, &msc_i_fsm, struct msc_i, ran);
+}
+
+/* Send Clear Command and wait for Clear Complete autonomously. "Normally", the MSC-A handles Clear Command and receives
+ * Clear Complete, and then terminates MSC-I directly. This is useful to replace an MSC-I with another MSC-I during
+ * Handover. */
+void msc_i_clear(struct msc_i *msc_i)
+{
+ if (!msc_i)
+ return;
+ /* sanity timeout */
+ osmo_fsm_inst_state_chg(msc_i->c.fi, MSC_I_ST_CLEARING, 60, 0);
+}
+
+void msc_i_cleared(struct msc_i *msc_i)
+{
+ if (!msc_i)
+ return;
+ osmo_fsm_inst_state_chg(msc_i->c.fi, MSC_I_ST_CLEARED, 0, 0);
+}
+
+void msc_i_set_ran_conn(struct msc_i *msc_i, struct ran_conn *new_conn)
+{
+ struct ran_conn *old_conn = msc_i->ran_conn;
+
+ if (old_conn == new_conn)
+ return;
+
+ msc_i->ran_conn = NULL;
+ if (old_conn) {
+ old_conn->msc_role = NULL;
+ ran_conn_close(old_conn);
+ }
+
+ /* Taking a conn over from another MSC role? Make sure the other side forgets about it. */
+ if (new_conn->msc_role)
+ msc_role_forget_conn(new_conn->msc_role, new_conn);
+
+ msc_i->ran_conn = new_conn;
+ msc_i->ran_conn->msc_role = msc_i->c.fi;
+
+ /* Add the RAN conn info to the msub logging */
+ msub_update_id(msc_i->c.msub);
+}
diff --git a/src/libmsc/msc_i_remote.c b/src/libmsc/msc_i_remote.c
new file mode 100644
index 000000000..7b9598423
--- /dev/null
+++ b/src/libmsc/msc_i_remote.c
@@ -0,0 +1,245 @@
+/* The MSC-I role implementation variant that forwards requests to/from a remote MSC. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/msc_i_remote.h>
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/e_link.h>
+
+static struct osmo_fsm msc_i_remote_fsm;
+
+static struct msc_i *msc_i_remote_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &msc_i_remote_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+/* The idea is that this msc_i role is event-compatible to the "real" msc_i.c FSM, but instead of acting on the events
+ * directly, it forwards the events to a remote MSC-I role, via E-over-GSUP.
+ *
+ * [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
+ * you are here^
+ */
+static int msc_i_remote_msg_down_to_remote_msc(struct msc_i *msc_i,
+ enum osmo_gsup_message_type message_type,
+ struct an_apdu *an_apdu)
+{
+ struct osmo_gsup_message m;
+ struct e_link *e = msc_i->c.remote_to;
+
+ if (!e) {
+ LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "No E link to remote MSC, cannot send AN-APDU\n");
+ return -1;
+ }
+
+ if (e_prep_gsup_msg(e, &m)) {
+ LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Error composing E-interface GSUP message\n");
+ return -1;
+ }
+ m.message_type = message_type;
+ if (an_apdu) {
+ if (gsup_msg_assign_an_apdu(&m, an_apdu)) {
+ LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Error composing E-interface GSUP message\n");
+ return -1;
+ }
+ }
+
+ return e_tx(e, &m);
+}
+
+/* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-- msc_i_remote <---GSUP---- msc_a_remote <-- msc_i <--BSSMAP--- [BSS]
+ * you are here^
+ */
+static int msc_i_remote_rx_gsup(struct msc_i *msc_i, const struct osmo_gsup_message *gsup_msg)
+{
+ uint32_t event;
+ struct an_apdu an_apdu;
+ int rc;
+
+ /* MSC_A_EV_FROM_I_COMPLETE_LAYER_3 will never occur with a remote MSC-I, since all Complete Layer 3 will happen
+ * between a local MSC-A and local MSC-I roles. Only after an inter-MSC Handover will there possibly exist a
+ * remote MSC-I, which is long after Complete Layer 3. */
+
+ switch (gsup_msg->message_type) {
+ case OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ case OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_REQUEST:
+ event = MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST:
+ event = MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_CLOSE:
+ case OSMO_GSUP_MSGT_E_ABORT:
+ case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
+ msc_i_clear(msc_i);
+ return 0;
+
+ default:
+ LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ return -1;
+ };
+
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-- msc_i_remote <---GSUP---- msc_a_remote <-- msc_i <--BSSMAP--- [BSS]
+ * ^you are here
+ */
+ gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
+ rc = msub_role_dispatch(msc_i->c.msub, MSC_ROLE_A, event, &an_apdu);
+ if (an_apdu.msg)
+ msgb_free(an_apdu.msg);
+ return rc;
+}
+
+static void msc_i_remote_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_i *msc_i = msc_i_remote_priv(fi);
+ struct an_apdu *an_apdu;
+
+ switch (event) {
+
+ case MSC_REMOTE_EV_RX_GSUP:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-- msc_i_remote <---GSUP---- msc_a_remote <-- msc_i <--BSSMAP--- [BSS]
+ * you are here^
+ */
+ msc_i_remote_rx_gsup(msc_i, (const struct osmo_gsup_message*)data);
+ return;
+
+ case MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+ an_apdu = data;
+ msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST, an_apdu);
+ return;
+
+ case MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+ an_apdu = data;
+ msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_RESULT, an_apdu);
+ return;
+
+ case MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+ an_apdu = data;
+ msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_ERROR, an_apdu);
+ return;
+
+ case MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+ an_apdu = data;
+ msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_RESULT, an_apdu);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_i_remote_fsm_clearing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
+}
+
+static void msc_i_remote_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_i *msc_i = msc_i_remote_priv(fi);
+ msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_CLOSE, NULL);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_i_remote_fsm_states[] = {
+ [MSC_I_ST_READY] = {
+ .name = "READY",
+ .action = msc_i_remote_fsm_ready,
+ .in_event_mask = 0
+ | S(MSC_REMOTE_EV_RX_GSUP)
+ | S(MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT)
+ | S(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR)
+ ,
+ .out_state_mask = 0
+ | S(MSC_I_ST_CLEARING)
+ ,
+ },
+ [MSC_I_ST_CLEARING] = {
+ .name = "CLEARING",
+ .onenter = msc_i_remote_fsm_clearing_onenter,
+ },
+};
+
+static struct osmo_fsm msc_i_remote_fsm = {
+ .name = "msc_i_remote",
+ .states = msc_i_remote_fsm_states,
+ .num_states = ARRAY_SIZE(msc_i_remote_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msc_i_fsm_event_names,
+ .cleanup = msc_i_remote_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msc_i_remote_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&msc_i_remote_fsm) == 0);
+}
+
+struct msc_i *msc_i_remote_alloc(struct msub *msub, struct ran_infra *ran, struct e_link *e)
+{
+ struct msc_i *msc_i;
+
+ msub_role_alloc(msub, MSC_ROLE_I, &msc_i_remote_fsm, struct msc_i, ran);
+ msc_i = msub_msc_i(msub);
+ if (!msc_i)
+ return NULL;
+
+ e_link_assign(e, msc_i->c.fi);
+ if (!msc_i->c.remote_to) {
+ LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Failed to set up E link over GSUP to remote MSC\n");
+ msc_i_clear(msc_i);
+ return NULL;
+ }
+
+ return msc_i;
+}
diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c
deleted file mode 100644
index e2c52dfda..000000000
--- a/src/libmsc/msc_ifaces.c
+++ /dev/null
@@ -1,143 +0,0 @@
-/* Implementation for MSC decisions which interface to send messages out on. */
-
-/* (C) 2016 by sysmocom s.m.f.c GmbH <info@sysmocom.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <osmocom/core/logging.h>
-
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/gsm_data.h>
-#include <osmocom/msc/msc_ifaces.h>
-#include <osmocom/msc/gsm_subscriber.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/mgcp_client/mgcp_client.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/sgs_iface.h>
-#include <osmocom/msc/gsm_04_08.h>
-#include <osmocom/msc/msc_mgcp.h>
-
-#include "../../bscconfig.h"
-
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#else
-#include <osmocom/msc/iu_dummy.h>
-#endif /* BUILD_IU */
-
-static int msc_tx(struct ran_conn *conn, struct msgb *msg)
-{
- if (!msg)
- return -EINVAL;
- if (!conn) {
- msgb_free(msg);
- return -EINVAL;
- }
-
- DEBUGP(DMSC, "msc_tx %u bytes to %s via %s\n",
- msg->len, vlr_subscr_name(conn->vsub),
- osmo_rat_type_name(conn->via_ran));
- switch (conn->via_ran) {
- case OSMO_RAT_GERAN_A:
- msg->dst = conn;
- return a_iface_tx_dtap(msg);
-
- case OSMO_RAT_UTRAN_IU:
- msg->dst = conn->iu.ue_ctx;
- return ranap_iu_tx(msg, 0);
-
- case OSMO_RAT_EUTRAN_SGS:
- msg->dst = conn;
- return sgs_iface_tx_dtap_ud(msg);
-
- default:
- LOGP(DMSC, LOGL_ERROR,
- "msc_tx(): conn->via_ran invalid (%d)\n",
- conn->via_ran);
- msgb_free(msg);
- return -1;
- }
-}
-
-
-int msc_tx_dtap(struct ran_conn *conn,
- struct msgb *msg)
-{
- return msc_tx(conn, msg);
-}
-
-
-/* 9.2.5 CM service accept */
-int msc_gsm48_tx_mm_serv_ack(struct ran_conn *conn)
-{
- 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;
-
- DEBUGP(DMM, "-> CM SERVICE ACCEPT %s\n",
- vlr_subscr_name(conn->vsub));
-
- return msc_tx_dtap(conn, msg);
-}
-
-/* 9.2.6 CM service reject */
-int msc_gsm48_tx_mm_serv_rej(struct ran_conn *conn,
- enum gsm48_reject_value value)
-{
- struct msgb *msg;
-
- if (!conn)
- return -EINVAL;
-
- msg = gsm48_create_mm_serv_rej(value);
- if (!msg) {
- LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n");
- return -1;
- }
-
- DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value);
-
- return msc_tx_dtap(conn, msg);
-}
-
-int msc_tx_common_id(struct ran_conn *conn)
-{
- if (!conn)
- return -EINVAL;
-
- /* Common ID is only sent over IuCS */
- if (conn->via_ran != OSMO_RAT_UTRAN_IU) {
- LOGP(DMM, LOGL_INFO,
- "%s: Asked to transmit Common ID, but skipping"
- " because this is not on UTRAN\n",
- vlr_subscr_name(conn->vsub));
- return 0;
- }
-
- DEBUGP(DIUCS, "%s: tx CommonID %s\n",
- vlr_subscr_name(conn->vsub), conn->vsub->imsi);
- return ranap_iu_tx_common_id(conn->iu.ue_ctx, conn->vsub->imsi);
-}
diff --git a/src/libmsc/msc_mgcp.c b/src/libmsc/msc_mgcp.c
deleted file mode 100644
index 5c8888031..000000000
--- a/src/libmsc/msc_mgcp.c
+++ /dev/null
@@ -1,1254 +0,0 @@
-/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
- * All Rights Reserved
- *
- * Author: Philipp Maier
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include <arpa/inet.h>
-
-#include <osmocom/mgcp_client/mgcp_client.h>
-#include <osmocom/core/logging.h>
-#include <osmocom/core/utils.h>
-#include <osmocom/core/timer.h>
-#include <osmocom/core/fsm.h>
-#include <osmocom/core/byteswap.h>
-#include <osmocom/msc/msc_mgcp.h>
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/msc_ifaces.h>
-#include <osmocom/msc/gsm_04_08.h>
-#include <osmocom/msc/iucs.h>
-#include <osmocom/msc/vlr.h>
-
-#include "../../bscconfig.h"
-
-#define S(x) (1 << (x))
-
-#define CONN_ID_RAN 1
-#define CONN_ID_CN 2
-
-#define MGCP_MGW_TIMEOUT 4 /* in seconds */
-#define MGCP_MGW_TIMEOUT_TIMER_NR 1
-#define MGCP_RAN_TIMEOUT 120 /* in seconds */
-#define MGCP_RAN_TIMEOUT_TIMER_NR 2
-#define MGCP_REL_TIMEOUT 60 /* in seconds */
-#define MGCP_REL_TIMEOUT_TIMER_NR 3
-#define MGCP_ASS_TIMEOUT 10 /* in seconds */
-#define MGCP_ASS_TIMEOUT_TIMER_NR 4
-
-/* Some internal cause codes to indicate fault condition inside the FSM */
-enum msc_mgcp_cause_code {
- MGCP_ERR_MGW_FAIL,
- MGCP_ERR_MGW_INVAL_RESP,
- MGCP_ERR_MGW_TX_FAIL,
- MGCP_ERR_MGW_TIMEOUT,
- MGCP_ERR_UNEXP_TEARDOWN,
- MGCP_ERR_UNSUPP_ADDR_FMT,
- MGCP_ERR_RAN_TIMEOUT,
- MGCP_ERR_ASS_TIMEOUT,
- MGCP_ERR_TOOLONG,
- MGCP_ERR_ASSGMNT_FAIL
-};
-
-/* Human readable respresentation of the faul codes, will be displayed by
- * handle_error() */
-static const struct value_string msc_mgcp_cause_codes_names[] = {
- {MGCP_ERR_MGW_FAIL, "operation failed on MGW"},
- {MGCP_ERR_MGW_INVAL_RESP, "invalid / unparseable response from MGW"},
- {MGCP_ERR_MGW_TX_FAIL, "failed to transmit MGCP message to MGW"},
- {MGCP_ERR_MGW_TIMEOUT, "request to MGW timed out"},
- {MGCP_ERR_UNEXP_TEARDOWN, "unexpected connection teardown"},
- {MGCP_ERR_UNSUPP_ADDR_FMT, "unsupported network address format used (RAN)"},
- {MGCP_ERR_RAN_TIMEOUT, "call could not be completed in time (RAN)"},
- {MGCP_ERR_ASS_TIMEOUT, "assignment could not be completed in time (RAN)"},
- {MGCP_ERR_TOOLONG, "string value too long"},
- {MGCP_ERR_ASSGMNT_FAIL, "assignment failure (RAN)"},
- {0, NULL}
-};
-
-enum fsm_msc_mgcp_states {
- ST_CRCX_RAN,
- ST_CRCX_CN,
- ST_CRCX_COMPL,
- ST_MDCX_CN,
- ST_MDCX_CN_COMPL,
- ST_MDCX_RAN,
- ST_MDCX_RAN_COMPL,
- ST_CALL,
- ST_HALT,
-};
-
-enum msc_mgcp_fsm_evt {
- /* Initial event: start off the state machine */
- EV_INIT,
-
- /* External event: Notify that the Assignment is complete and we
- * may now forward IP/Port of the remote call leg to the MGW */
- EV_ASSIGN,
-
- /* External event: Notify that the Call is complete and that the
- * two half open connections on the MGW should now be connected */
- EV_CONNECT,
-
- /* External event: Notify that the call is over and the connections
- * on the mgw shall be removed */
- EV_TEARDOWN,
-
- /* Internal event: An error occurred that requires a controlled
- * teardown of the RTP connections */
- EV_TEARDOWN_ERROR,
-
- /* Internal event: The mgcp_gw has sent its CRCX response for
- * the RAN side */
- EV_CRCX_RAN_RESP,
-
- /* Internal event: The mgcp_gw has sent its CRCX response for
- * the CN side */
- EV_CRCX_CN_RESP,
-
- /* Internal event: The mgcp_gw has sent its MDCX response for
- * the RAN side */
- EV_MDCX_RAN_RESP,
-
- /* Internal event: The mgcp_gw has sent its MDCX response for
- * the CN side */
- EV_MDCX_CN_RESP,
-
- /* Internal event: The mgcp_gw has sent its DLCX response for
- * the RAN and CN side */
- EV_DLCX_ALL_RESP,
-};
-
-static const struct value_string msc_mgcp_fsm_evt_names[] = {
- OSMO_VALUE_STRING(EV_INIT),
- OSMO_VALUE_STRING(EV_ASSIGN),
- OSMO_VALUE_STRING(EV_CONNECT),
- OSMO_VALUE_STRING(EV_TEARDOWN),
- OSMO_VALUE_STRING(EV_TEARDOWN_ERROR),
- OSMO_VALUE_STRING(EV_CRCX_RAN_RESP),
- OSMO_VALUE_STRING(EV_CRCX_CN_RESP),
- OSMO_VALUE_STRING(EV_MDCX_RAN_RESP),
- OSMO_VALUE_STRING(EV_MDCX_CN_RESP),
- OSMO_VALUE_STRING(EV_DLCX_ALL_RESP),
- {0, NULL}
-};
-
-/* A general error handler function. On error we still have an interest to
- * remove a half open connection (if possible). This function will execute
- * a controlled jump to the DLCX phase. From there, the FSM will then just
- * continue like the call were ended normally */
-#define handle_error(mgcp_ctx, cause, dlcx) _handle_error(mgcp_ctx, cause, dlcx, __FILE__, __LINE__)
-static void _handle_error(struct mgcp_ctx *mgcp_ctx, enum msc_mgcp_cause_code cause, bool dlcx, const char *file,
- int line)
-{
- bool dlcx_possible = true;
- struct osmo_fsm_inst *fi;
- struct gsm_mncc mncc;
-
- OSMO_ASSERT(mgcp_ctx);
- fi = mgcp_ctx->fsm;
- OSMO_ASSERT(fi);
-
- /* Check if the endpoint identifier is a specific endpoint identifier,
- * since in order to perform a DLCX we must know the specific
- * identifier of the endpoint we want to release. If we do not have
- * this information because of errornous communication we can not
- * perform a DLCX. */
- if (strstr(mgcp_ctx->rtp_endpoint, "*"))
- dlcx_possible = false;
-
- LOGPFSMLSRC(mgcp_ctx->fsm, LOGL_ERROR, file, line, "%s -- graceful shutdown...\n",
- get_value_string(msc_mgcp_cause_codes_names, cause));
-
- /* Request the higher layers (gsm_04_08.c) to release the call. If the
- * problem occured after msc_mgcp_call_release() was calls, remain
- * silent because we already got informed and the higher layers might
- * already freed their context information (trans). */
- if (!mgcp_ctx->free_ctx) {
- mncc = (struct gsm_mncc) {
- .msg_type = MNCC_REL_REQ,
- .callref = mgcp_ctx->trans->callref,
- .cause = {
- .location = GSM48_CAUSE_LOC_PRN_S_LU,
- .coding = 0, /* FIXME */
- .value = GSM48_CC_CAUSE_RESOURCE_UNAVAIL
- }
- };
-
- mncc_set_cause(&mncc, GSM48_CAUSE_LOC_TRANS_NET,
- GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
- mncc_tx_to_cc(mgcp_ctx->trans->net, MNCC_REL_REQ, &mncc);
- }
-
- /* For the shutdown we have two options. Whenever it makes sense to
- * send a DLCX to the MGW in order to be sure that the connection is
- * properly cleaned up, the dlcx flag should be set. In other cases
- * where a DLCX does not make sense (e.g. the MGW times out), halting
- * directly is the better options. In those cases, the dlcx flag
- * should not be set */
- if (dlcx && dlcx_possible) {
- /* Fast-forward the FSM into call state. In this state the FSM
- * expects either an EV_TEARDOWN or an EV_TEARDOWN_ERROR. When
- * one of the two events is received a DLCX will be send to
- * the MGW. After that. The FSM automatically halts but will
- * still expect a call msc_mgcp_call_release() to be freed
- * completely */
- osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN_ERROR, mgcp_ctx);
- } else {
- /* Halt the state machine immediately. The FSM will not be
- * freed yet, we stil require the higher layers to call
- * msc_mgcp_call_release() */
- osmo_fsm_inst_state_chg(fi, ST_HALT, 0, 0);
- osmo_fsm_inst_dispatch(fi, EV_TEARDOWN_ERROR, mgcp_ctx);
- }
-}
-
-/* Timer callback to shut down in case of connectivity problems */
-static int fsm_timeout_cb(struct osmo_fsm_inst *fi)
-{
- struct mgcp_ctx *mgcp_ctx = fi->priv;
- struct mgcp_client *mgcp;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
-
- if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
- /* We were unable to communicate with the MGW, unfortunately
- * there is no meaningful action we can take now other than
- * giving up. */
-
- /* Cancel the transaction that timed out */
- mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans);
-
- /* halt of the FSM */
- handle_error(mgcp_ctx, MGCP_ERR_MGW_TIMEOUT, false);
- } else if (fi->T == MGCP_RAN_TIMEOUT_TIMER_NR) {
- /* If the logic that controls the RAN is unable to negotiate a
- * connection, we presumably still have a working connection to
- * the MGW, we will try to shut down gracefully. */
- handle_error(mgcp_ctx, MGCP_ERR_RAN_TIMEOUT, true);
- } else if (fi->T == MGCP_REL_TIMEOUT_TIMER_NR) {
- /* Under normal conditions, the MSC logic should always command
- * to release the call at some point. However, the release may
- * be missing due to errors in the MSC logic and we may have
- * reached ST_HALT because of cascading errors and timeouts. In
- * this and only in this case we will allow ST_HALT to free all
- * context information on its own authority. */
- mgcp_ctx->free_ctx = true;
-
- /* Initiate self destruction of the FSM */
- osmo_fsm_inst_state_chg(fi, ST_HALT, 0, 0);
- osmo_fsm_inst_dispatch(fi, EV_TEARDOWN, mgcp_ctx);
- } else if (fi->T == MGCP_ASS_TIMEOUT_TIMER_NR) {
- /* There may be rare cases in which the MSC is unable to
- * complete the call assignment */
- handle_error(mgcp_ctx, MGCP_ERR_ASS_TIMEOUT, true);
- } else {
- /* Ther must not be any unsolicited timers in this FSM. If so,
- * we have serious problem. */
- OSMO_ASSERT(false);
- }
-
- return 0;
-}
-
-static void mgw_crcx_ran_resp_cb(struct mgcp_response *r, void *priv);
-
-/* Callback for ST_CRCX_RAN: Send CRCX for RAN side to MGW */
-static void fsm_crcx_ran_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct mgcp_client *mgcp;
- struct mgcp_msg mgcp_msg;
- struct msgb *msg;
- int rc;
- struct gsm_trans *trans;
- struct ran_conn *conn;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- /* NOTE: In case of error, we will not be able to perform any DLCX
- * operation because until this point we do not have requested any
- * endpoint yet. */
-
- LOGPFSML(fi, LOGL_DEBUG,
- "CRCX/RAN: creating connection for the RAN side on MGW endpoint:%s...\n", mgcp_ctx->rtp_endpoint);
-
- /* Generate MGCP message string */
- mgcp_msg = (struct mgcp_msg) {
- .verb = MGCP_VERB_CRCX,
- .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE),
- .call_id = mgcp_ctx->call_id,
- .conn_mode = MGCP_CONN_RECV_ONLY
- };
- if (osmo_strlcpy(mgcp_msg.endpoint, mgcp_client_rtpbridge_wildcard(mgcp), sizeof(mgcp_msg.endpoint)) >=
- MGCP_ENDPOINT_MAXLEN) {
- handle_error(mgcp_ctx, MGCP_ERR_TOOLONG, false);
- return;
- }
-
- /* HACK: We put the connection in loopback mode at the beginnig to
- * trick the hNodeB into doing the IuUP negotiation with itself.
- * This is a hack we need because osmo-mgw does not support IuUP yet, see OS#2459. */
-#ifdef BUILD_IU
- if (conn->via_ran == OSMO_RAT_UTRAN_IU)
- mgcp_msg.conn_mode = MGCP_CONN_LOOPBACK;
-#endif
-
- msg = mgcp_msg_gen(mgcp, &mgcp_msg);
- OSMO_ASSERT(msg);
-
- /* Transmit MGCP message to MGW */
- mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
- rc = mgcp_client_tx(mgcp, msg, mgw_crcx_ran_resp_cb, mgcp_ctx);
- if (rc < 0) {
- handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL, false);
- return;
- }
-
- osmo_fsm_inst_state_chg(fi, ST_CRCX_CN, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
-}
-
-/* Callback for MGCP-Client: handle response for RAN associated CRCX */
-static void mgw_crcx_ran_resp_cb(struct mgcp_response *r, void *priv)
-{
- struct mgcp_ctx *mgcp_ctx = priv;
- int rc;
- struct gsm_trans *trans;
- struct ran_conn *conn;
-
- /* NOTE: In case of error, we will not be able to perform any DLCX
- * operation because until we either get a parseable message that
- * contains an error code (no endpoint is seized in those cases)
- * or we get an unparseable message. In this case we can not be
- * sure, but we also can not draw any assumptions from unparseable
- * messages. */
-
- OSMO_ASSERT(mgcp_ctx);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- if (r->head.response_code != 200) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
- "CRCX/RAN: response yields error: %d %s\n", r->head.response_code, r->head.comment);
- handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL, false);
- return;
- }
-
- /* memorize connection identifier and specific endpoint id */
- osmo_strlcpy(mgcp_ctx->conn_id_ran, r->head.conn_id, sizeof(mgcp_ctx->conn_id_ran));
- LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/RAN: MGW responded with CI: %s\n", mgcp_ctx->conn_id_ran);
- osmo_strlcpy(mgcp_ctx->rtp_endpoint, r->head.endpoint, sizeof(mgcp_ctx->rtp_endpoint));
- LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/RAN: MGW assigned endpoint: %s\n", mgcp_ctx->rtp_endpoint);
-
- rc = mgcp_response_parse_params(r);
- if (rc) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/RAN: Cannot parse response\n");
- handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP, false);
- return;
- }
-
- LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/RAN: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
-
- conn->rtp.local_port_ran = r->audio_port;
- osmo_strlcpy(conn->rtp.local_addr_ran, r->audio_ip, sizeof(conn->rtp.local_addr_ran));
-
- /* Notify the FSM that we got the response. */
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_RAN_RESP, mgcp_ctx);
-}
-
-static void mgw_crcx_cn_resp_cb(struct mgcp_response *r, void *priv);
-
-/* Callback for ST_CRCX_CN: check MGW response and send CRCX for CN side to MGW */
-static void fsm_crcx_cn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct mgcp_client *mgcp;
- struct mgcp_msg mgcp_msg;
- struct msgb *msg;
- int rc;
- struct gsm_trans *trans;
- struct ran_conn *conn;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- switch (event) {
- case EV_CRCX_RAN_RESP:
- break;
- default:
- handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN, true);
- return;
- }
-
- LOGPFSML(fi, LOGL_DEBUG,
- "CRCX/CN creating connection for the CN side on MGW endpoint:%s...\n", mgcp_ctx->rtp_endpoint);
-
- /* Generate MGCP message string */
- mgcp_msg = (struct mgcp_msg) {
- .verb = MGCP_VERB_CRCX,
- .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE),
- .call_id = mgcp_ctx->call_id,
- .conn_mode = MGCP_CONN_RECV_ONLY
- };
- if (osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->rtp_endpoint, sizeof(mgcp_msg.endpoint)) >=
- MGCP_ENDPOINT_MAXLEN) {
- handle_error(mgcp_ctx, MGCP_ERR_TOOLONG, true);
- return;
- }
-
- msg = mgcp_msg_gen(mgcp, &mgcp_msg);
- OSMO_ASSERT(msg);
-
- /* Transmit MGCP message to MGW */
- mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
- rc = mgcp_client_tx(mgcp, msg, mgw_crcx_cn_resp_cb, mgcp_ctx);
- if (rc < 0) {
- handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL, true);
- return;
- }
-
- osmo_fsm_inst_state_chg(fi, ST_CRCX_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
-}
-
-/* Callback for MGCP-Client: handle response for CN associated CRCX */
-static void mgw_crcx_cn_resp_cb(struct mgcp_response *r, void *priv)
-{
- struct mgcp_ctx *mgcp_ctx = priv;
- int rc;
- struct gsm_trans *trans;
- struct ran_conn *conn;
-
- OSMO_ASSERT(mgcp_ctx);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- if (r->head.response_code != 200) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
- "CRCX/CN: response yields error: %d %s\n", r->head.response_code, r->head.comment);
- handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL, true);
- return;
- }
-
- /* memorize connection identifier */
- osmo_strlcpy(mgcp_ctx->conn_id_cn, r->head.conn_id, sizeof(mgcp_ctx->conn_id_cn));
- LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/CN: MGW responded with CI: %s\n", mgcp_ctx->conn_id_cn);
-
- rc = mgcp_response_parse_params(r);
- if (rc) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/CN: Cannot parse response\n");
- handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP, true);
- return;
- }
-
- LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/CN: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
-
- conn->rtp.local_port_cn = r->audio_port;
- osmo_strlcpy(conn->rtp.local_addr_cn, r->audio_ip, sizeof(conn->rtp.local_addr_cn));
-
- /* Notify the FSM that we got the response. */
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_CN_RESP, mgcp_ctx);
-}
-
-/* Callback for ST_CRCX_COMPL: check MGW response, start assignment */
-static void fsm_crcx_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct gsm_trans *trans;
- struct ran_conn *conn;
-
- OSMO_ASSERT(mgcp_ctx);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- switch (event) {
- case EV_CRCX_CN_RESP:
- break;
- default:
- handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN, true);
- return;
- }
-
- /* Forward assignment request to A/RANAP */
- if (conn->via_ran == OSMO_RAT_UTRAN_IU) {
-#ifdef BUILD_IU
- /* Assign a voice channel via RANAP on 3G */
- if (iu_rab_act_cs(trans))
- goto error;
-#else
- LOGPFSML(fi, LOGL_ERROR, "Cannot send Iu RAB Assignment: built without Iu support\n");
- goto error;
-#endif
- } else if (conn->via_ran == OSMO_RAT_GERAN_A) {
- /* Assign a voice channel via A on 2G */
- if (a_iface_tx_assignment(trans))
- goto error;
- } else {
- /* Unset or unimplemented new RAN type */
- LOGPFSML(fi, LOGL_ERROR, "Unknown RAN type: %d\n", conn->via_ran);
- return;
- }
-
- /* Respond back to MNCC (if requested) */
- if (trans->tch_rtp_create) {
- if (gsm48_tch_rtp_create(trans))
- goto error;
- }
-
- /* Note: When we reach this point then the situation is basically that
- * we have two sides connected, both are in loopback. The local ports
- * of the side pointing towards the BSS should be already communicated
- * and we are waiting now the other end to pick up. */
- osmo_fsm_inst_state_chg(fi, ST_MDCX_CN, MGCP_RAN_TIMEOUT, MGCP_RAN_TIMEOUT_TIMER_NR);
- return;
-
-error:
- handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL, true);
-}
-
-static void mgw_mdcx_cn_resp_cb(struct mgcp_response *r, void *priv);
-
-/* Callback for ST_MDCX_CN: send MDCX for RAN side to MGW */
-static void fsm_mdcx_cn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct mgcp_client *mgcp;
- struct gsm_trans *trans;
- struct ran_conn *conn;
- struct mgcp_msg mgcp_msg;
- struct msgb *msg;
- int rc;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- switch (event) {
- case EV_CONNECT:
- break;
- default:
- handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN, true);
- return;
- }
-
- LOGPFSML(fi, LOGL_DEBUG,
- "MDCX/CN: completing connection for the CN side on MGW endpoint:%p, remote leg expects RTP input on address %s:%u\n",
- mgcp_ctx->rtp_endpoint, conn->rtp.remote_addr_cn, conn->rtp.remote_port_cn);
-
- /* Generate MGCP message string */
- mgcp_msg = (struct mgcp_msg) {
- .verb = MGCP_VERB_MDCX,
- .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
- MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |
- MGCP_MSG_PRESENCE_AUDIO_PORT),
- .call_id = mgcp_ctx->call_id,
- .conn_id = mgcp_ctx->conn_id_cn,
- .conn_mode = MGCP_CONN_RECV_SEND,
- .audio_ip = conn->rtp.remote_addr_cn,
- .audio_port = conn->rtp.remote_port_cn,
- .codecs[0] = conn->rtp.codec_cn,
- .codecs_len = 1
- };
- if (osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->rtp_endpoint, sizeof(mgcp_msg.endpoint)) >=
- MGCP_ENDPOINT_MAXLEN) {
- handle_error(mgcp_ctx, MGCP_ERR_TOOLONG, true);
- return;
- }
-
- msg = mgcp_msg_gen(mgcp, &mgcp_msg);
- OSMO_ASSERT(msg);
-
- /* Transmit MGCP message to MGW */
- mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
- rc = mgcp_client_tx(mgcp, msg, mgw_mdcx_cn_resp_cb, mgcp_ctx);
- if (rc < 0) {
- handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL, true);
- return;
- }
-
- osmo_fsm_inst_state_chg(fi, ST_MDCX_CN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
-}
-
-/* Callback for MGCP-Client: handle response for CN associated CRCX */
-static void mgw_mdcx_cn_resp_cb(struct mgcp_response *r, void *priv)
-{
- struct mgcp_ctx *mgcp_ctx = priv;
-
- OSMO_ASSERT(mgcp_ctx);
-
- if (r->head.response_code != 200) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
- "MDCX/CN: response yields error: %d %s\n", r->head.response_code, r->head.comment);
- handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL, true);
- return;
- }
-
- /* Notify the FSM that we got the response. */
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_CN_RESP, mgcp_ctx);
-}
-
-/* Callback for ST_MDCX_CN_COMPL: wait for mgw response, move on with the MDCX
- * for the RAN side if we already have valid IP/Port data for the RAN sided
- * RTP stream. */
-static void fsm_mdcx_cn_compl_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct ran_conn *conn;
- struct gsm_trans *trans;
-
- OSMO_ASSERT(mgcp_ctx);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- switch (event) {
- case EV_MDCX_CN_RESP:
- break;
- default:
- handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN, true);
- return;
- }
-
- /* Enter MDCX phase, but we must be sure that the Assigmnet on the A or
- * IuCS interface is complete (IP-Address and Port are valid) */
- osmo_fsm_inst_state_chg(fi, ST_MDCX_RAN, MGCP_ASS_TIMEOUT, MGCP_ASS_TIMEOUT_TIMER_NR);
-
- /* If we already have a valid remote port and IP-Address from the RAN side
- * call leg, the assignment has been completed before we got here, so we
- * may move on immediately */
- if (conn->rtp.remote_port_ran != 0 || strlen(conn->rtp.remote_addr_ran) > 0)
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASSIGN, mgcp_ctx);
-}
-
-static void mgw_mdcx_ran_resp_cb(struct mgcp_response *r, void *priv);
-
-/* Callback for ST_MDCX_RAN: wait for assignment completion, send MDCX for CN side to MGW */
-static void fsm_mdcx_ran_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct mgcp_client *mgcp;
- struct gsm_trans *trans;
- struct ran_conn *conn;
- struct mgcp_msg mgcp_msg;
- struct msgb *msg;
- int rc;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
- trans = mgcp_ctx->trans;
- OSMO_ASSERT(trans);
- conn = trans->conn;
- OSMO_ASSERT(conn);
-
- switch (event) {
- case EV_ASSIGN:
- break;
- default:
- handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN, true);
- return;
- }
-
- LOGPFSML(fi, LOGL_DEBUG,
- "MDCX/RAN: completing connection for the CN side on MGW endpoint:%p, RAN expects RTP input on address %s:%u\n",
- mgcp_ctx->rtp_endpoint, conn->rtp.remote_addr_ran, conn->rtp.remote_port_ran);
-
- /* Generate MGCP message string */
- mgcp_msg = (struct mgcp_msg) {
- .verb = MGCP_VERB_MDCX,
- .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
- MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |
- MGCP_MSG_PRESENCE_AUDIO_PORT),
- .call_id = mgcp_ctx->call_id,
- .conn_id = mgcp_ctx->conn_id_ran,
- .conn_mode = MGCP_CONN_RECV_SEND,
- .audio_ip = conn->rtp.remote_addr_ran,
- .audio_port = conn->rtp.remote_port_ran,
- .codecs[0] = conn->rtp.codec_ran,
- .codecs_len = 1
- };
- if (osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->rtp_endpoint, sizeof(mgcp_msg.endpoint)) >=
- MGCP_ENDPOINT_MAXLEN) {
- handle_error(mgcp_ctx, MGCP_ERR_TOOLONG, true);
- return;
- }
-
- msg = mgcp_msg_gen(mgcp, &mgcp_msg);
- OSMO_ASSERT(msg);
-
- /* Transmit MGCP message to MGW */
- mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
- rc = mgcp_client_tx(mgcp, msg, mgw_mdcx_ran_resp_cb, mgcp_ctx);
- if (rc < 0) {
- handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL, true);
- return;
- }
-
- osmo_fsm_inst_state_chg(fi, ST_MDCX_RAN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
-}
-
-/* Callback for MGCP-Client: handle response for CN associated CRCX */
-static void mgw_mdcx_ran_resp_cb(struct mgcp_response *r, void *priv)
-{
- struct mgcp_ctx *mgcp_ctx = priv;
-
- OSMO_ASSERT(mgcp_ctx);
-
- if (r->head.response_code != 200) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
- "MDCX/RAN: response yields error: %d %s\n", r->head.response_code, r->head.comment);
- handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL, true);
- return;
- }
-
- /* Notify the FSM that we got the response. */
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_RAN_RESP, mgcp_ctx);
-}
-
-/* Callback for ST_MDCX_RAN_COMPL: check MGW response */
-static void fsm_mdcx_ran_compl_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- OSMO_ASSERT(mgcp_ctx);
-
- switch (event) {
- case EV_MDCX_RAN_RESP:
- break;
- default:
- handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN, true);
- return;
- }
-
- LOGPFSML(fi, LOGL_DEBUG, "call active, waiting for teardown...\n");
- osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
-}
-
-static void mgw_dlcx_all_resp_cb(struct mgcp_response *r, void *priv);
-
-/* Callback for ST_CALL: call is active, send DLCX for both sides on teardown */
-static void fsm_call_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-
- struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
- struct mgcp_client *mgcp;
- struct mgcp_msg mgcp_msg;
- struct msgb *msg;
- int rc;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
-
- LOGPFSML(fi, LOGL_DEBUG,
- "DLCX: removing connection for the RAN and CN side on MGW endpoint:%s...\n", mgcp_ctx->rtp_endpoint);
-
- /* Generate MGCP message string */
- mgcp_msg = (struct mgcp_msg) {
- .verb = MGCP_VERB_DLCX,
- .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID),
- .call_id = mgcp_ctx->call_id
- };
- if (osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->rtp_endpoint, sizeof(mgcp_msg.endpoint)) >=
- MGCP_ENDPOINT_MAXLEN) {
- handle_error(mgcp_ctx, MGCP_ERR_TOOLONG, true);
- return;
- }
-
- msg = mgcp_msg_gen(mgcp, &mgcp_msg);
- OSMO_ASSERT(msg);
-
- /* Transmit MGCP message to MGW */
- mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
- rc = mgcp_client_tx(mgcp, msg, mgw_dlcx_all_resp_cb, mgcp_ctx);
- if (rc < 0) {
- handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL, true);
- return;
- }
-
- osmo_fsm_inst_state_chg(fi, ST_HALT, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
-}
-
-/* Callback for MGCP-Client: handle response for CN associated CRCX */
-static void mgw_dlcx_all_resp_cb(struct mgcp_response *r, void *priv)
-{
- struct mgcp_ctx *mgcp_ctx = priv;
-
- OSMO_ASSERT(mgcp_ctx);
-
- /* DLCX is the only command where 250 is permitted as positive result */
- if (r->head.response_code != 200 && r->head.response_code != 250) {
- LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
- "DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
- handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL, true);
- return;
- }
-
- /* Notify the FSM that we got the response. */
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_DLCX_ALL_RESP, mgcp_ctx);
-}
-
-/* Callback for ST_HALT: Terminate the state machine */
-static void fsm_halt_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgcp_ctx *mgcp_ctx = data;
- struct mgcp_client *mgcp;
-
- OSMO_ASSERT(mgcp_ctx);
- mgcp = mgcp_ctx->mgcp;
- OSMO_ASSERT(mgcp);
-
- /* NOTE: We must not free the context information now, we have to
- * wait until msc_mgcp_call_release() is called. Then we are sure
- * that the logic controlling us is fully aware that the context
- * information is freed. If we would free early now the controlling
- * logic might mistakenly think that the context info is still alive,
- * so lets keep the context info until we are explicitly asked for
- * throwing it away. */
- if (mgcp_ctx->free_ctx) {
- /* Be sure that there is no pending MGW transaction */
- mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans);
-
- /* Free FSM and its context information */
- osmo_fsm_inst_free(mgcp_ctx->fsm);
- talloc_free(mgcp_ctx);
- return;
- }
-
- osmo_fsm_inst_state_chg(fi, ST_HALT, MGCP_REL_TIMEOUT, MGCP_REL_TIMEOUT_TIMER_NR);
-}
-
-static struct osmo_fsm_state fsm_msc_mgcp_states[] = {
-
- /* Startup state machine, send CRCX for RAN side. */
- [ST_CRCX_RAN] = {
- .in_event_mask = S(EV_INIT),
- .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_CN),
- .name = OSMO_STRINGIFY(ST_CRCX_RAN),
- .action = fsm_crcx_ran_cb,
- },
- /* When the response to the RAN CRCX is received, then proceed with
- sending the CRCX for CN side */
- [ST_CRCX_CN] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_CRCX_RAN_RESP),
- .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_COMPL),
- .name = OSMO_STRINGIFY(ST_CRCX_CN),
- .action = fsm_crcx_cn_cb,
- },
- /* Complete the CRCX phase by starting the assignment. Depending on the
- * RAT (Radio Access Technology), this will either trigger an
- * Assignment Request on the A-Interface or an RAB-Assignment on the
- * IU-interface */
- [ST_CRCX_COMPL] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_CRCX_CN_RESP),
- .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_CN),
- .name = OSMO_STRINGIFY(ST_CRCX_COMPL),
- .action = fsm_crcx_compl,
- },
- /* Wait for MSC to complete the assignment request, when complete, we
- * will enter the MDCX phase by sending an MDCX for the CN side to the
- * MGW */
- [ST_MDCX_CN] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_CONNECT),
- .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_CN_COMPL),
- .name = OSMO_STRINGIFY(ST_MDCX_CN),
- .action = fsm_mdcx_cn_cb,
- },
- /* We arrive in this state when the MDCX phase for the CN side has
- * completed we will check the IP/Port of the RAN connection. If this
- * data is valid we may continue with the MDCX phase for the RAN side.
- * If not we wait until the assinment completes on the A or on the IuCS
- * interface. The completion of the assignment will fill in the port and
- * IP-Address of the RAN side and way may continue then. */
- [ST_MDCX_CN_COMPL] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_MDCX_CN_RESP),
- .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_RAN),
- .name = OSMO_STRINGIFY(ST_MDCX_CN_COMPL),
- .action = fsm_mdcx_cn_compl_cb,
- },
- /* When the response for the CN MDCX is received, send the MDCX for the
- * RAN side to the MGW */
- [ST_MDCX_RAN] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_ASSIGN),
- .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_RAN_COMPL),
- .name = OSMO_STRINGIFY(ST_MDCX_RAN),
- .action = fsm_mdcx_ran_cb,
- },
- /* The RAN side MDCX phase is complete when the response is received
- * from the MGW. The call is then active, we change to ST_CALL and wait
- * there until the call ends. */
- [ST_MDCX_RAN_COMPL] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_MDCX_RAN_RESP),
- .out_state_mask = S(ST_HALT) | S(ST_CALL),
- .name = OSMO_STRINGIFY(ST_MDCX_RAN_COMPL),
- .action = fsm_mdcx_ran_compl_cb,
- },
- /* We are now in the active call phase, wait until the call is done
- * and send a DLCX then to remove all connections from the MGW */
- [ST_CALL] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR),
- .out_state_mask = S(ST_HALT),
- .name = OSMO_STRINGIFY(ST_CALL),
- .action = fsm_call_cb,
- },
- /* When the MGW confirms that the connections are terminated, then halt
- * the state machine. */
- [ST_HALT] = {
- .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_DLCX_ALL_RESP),
- .out_state_mask = S(ST_HALT),
- .name = OSMO_STRINGIFY(ST_HALT),
- .action = fsm_halt_cb,
- },
-};
-
-/* State machine definition */
-static struct osmo_fsm fsm_msc_mgcp = {
- .name = "msc-mgcp",
- .states = fsm_msc_mgcp_states,
- .num_states = ARRAY_SIZE(fsm_msc_mgcp_states),
- .log_subsys = DMGCP,
- .timer_cb = fsm_timeout_cb,
- .event_names = msc_mgcp_fsm_evt_names,
-};
-
-/* Try to invoke call assignment and set trans->cc.assignment_started flag if invoked.
- * This is relevant for already ongoing calls -- scenario:
- * - subscriber is in an active voice call,
- * - another call is coming in.
- * For the second call coming in, we must wait to establish RTP and assignment until the first call is CC-Disconnected.
- */
-int msc_mgcp_try_call_assignment(struct gsm_trans *trans)
-{
- struct ran_conn *conn = trans->conn;
- if (trans->cc.assignment_started)
- return 0;
- if (conn->rtp.mgcp_ctx) {
- LOGPFSMSL(conn->fi, DMGCP, LOGL_INFO, "Another call is already ongoing, not assigning yet\n");
- return 0;
- }
- LOGPFSMSL(conn->fi, DMGCP, LOGL_INFO, "Starting call assignment\n");
- trans->cc.assignment_started = true;
- return msc_mgcp_call_assignment(trans);
-}
-
-/* Notify that a new call begins. This will create a connection for the
- * RAN and the CN on the MGW.
- * Parameter:
- * trans: transaction context.
- * Returns -EINVAL on error, 0 on success. */
-int msc_mgcp_call_assignment(struct gsm_trans *trans)
-{
- struct mgcp_ctx *mgcp_ctx;
- static bool fsm_registered = false;
- struct ran_conn *conn;
- struct mgcp_client *mgcp;
-
- OSMO_ASSERT(trans);
-
- if (!trans->conn) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid conn, call assignment failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
-
- conn = trans->conn;
- mgcp = conn->network->mgw.client;
- OSMO_ASSERT(mgcp);
-
- if (conn->rtp.mgcp_ctx) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) double assignment detected, dropping...\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
-
-#ifdef BUILD_IU
- /* FIXME: HACK. where to scope the RAB Id? At the conn / subscriber / ranap_ue_conn_ctx? */
- static uint8_t next_iu_rab_id = 1;
- if (conn->via_ran == OSMO_RAT_UTRAN_IU)
- conn->iu.rab_id = next_iu_rab_id++;
-#endif
-
- /* Register the fsm description (if not already done) */
- if (fsm_registered == false) {
- osmo_fsm_register(&fsm_msc_mgcp);
- fsm_registered = true;
- }
-
- /* Allocate and configure a new fsm instance */
- mgcp_ctx = talloc_zero(NULL, struct mgcp_ctx);
- OSMO_ASSERT(mgcp_ctx);
-
- if (osmo_strlcpy(mgcp_ctx->rtp_endpoint, mgcp_client_rtpbridge_wildcard(mgcp), sizeof(mgcp_ctx->rtp_endpoint))
- >= sizeof(mgcp_ctx->rtp_endpoint)) {
- talloc_free(mgcp_ctx);
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) endpoint identifier exceeds maximum length: %s\n",
- vlr_subscr_name(trans->vsub), osmo_quote_str(mgcp_client_rtpbridge_wildcard(mgcp), -1));
- return -EINVAL;
- }
- mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm_msc_mgcp, NULL, NULL, LOGL_DEBUG, NULL);
- OSMO_ASSERT(mgcp_ctx->fsm);
- osmo_fsm_inst_update_id_f(mgcp_ctx->fsm, "%s_%s_trans%d",
- vlr_subscr_name(trans->vsub), ran_conn_get_conn_id(conn), trans->transaction_id);
- mgcp_ctx->fsm->priv = mgcp_ctx;
- mgcp_ctx->mgcp = mgcp;
- mgcp_ctx->trans = trans;
- mgcp_ctx->call_id = trans->callref;
-
- /* start state machine */
- OSMO_ASSERT(mgcp_ctx->fsm->state == ST_CRCX_RAN);
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_INIT, mgcp_ctx);
-
- conn->rtp.mgcp_ctx = mgcp_ctx;
-
- LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) call assignment initiated\n",
- vlr_subscr_name(conn->vsub));
-
- return 0;
-}
-
-/* Inform the FSM that the assignment (RAN connection) is now complete.
- * Parameter:
- * conn: RAN connection context.
- * port: port number of the remote leg.
- * addr: IP-address of the remote leg.
- * Returns -EINVAL on error, 0 on success. */
-int msc_mgcp_ass_complete(struct ran_conn *conn, uint16_t port, char *addr)
-{
- struct mgcp_ctx *mgcp_ctx;
-
- OSMO_ASSERT(conn);
-
- if (port == 0) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid remote call leg port, assignment completion failed\n",
- vlr_subscr_name(conn->vsub));
- return -EINVAL;
- }
- if (!addr || strlen(addr) <= 0) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) missing remote call leg address, assignment completion failed\n",
- vlr_subscr_name(conn->vsub));
- return -EINVAL;
- }
-
- mgcp_ctx = conn->rtp.mgcp_ctx;
- if (!mgcp_ctx) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid mgcp context, assignment completion failed.\n",
- vlr_subscr_name(conn->vsub));
- return -EINVAL;
- }
-
- /* Memorize port and IP-Address of the remote RAN call leg. We need this
- * information at latest when we enter the MDCX phase for the RAN side. */
- conn->rtp.remote_port_ran = port;
- osmo_strlcpy(conn->rtp.remote_addr_ran, addr, sizeof(conn->rtp.remote_addr_ran));
-
- LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) assignment completed, rtp %s:%d.\n",
- vlr_subscr_name(conn->vsub), conn->rtp.remote_addr_ran, port);
-
- /* Note: We only dispatch the event if we are really waiting for the
- * assignment, if we are not yet waiting, there is no need to loudly
- * broadcast an event that the all other states do not understand anyway */
- if (mgcp_ctx->fsm->state == ST_MDCX_RAN)
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASSIGN, mgcp_ctx);
-
- return 0;
-}
-
-/* Notify the MGCP context that Assignment failed.
- * This will end the "ringing" on the other call leg, and will usually result in L3 and conn release (i.e. when no other
- * transactions are still pending, which is usually the case). */
-int msc_mgcp_ass_fail(struct ran_conn *conn)
-{
- struct mgcp_ctx *mgcp_ctx;
-
- OSMO_ASSERT(conn);
-
- mgcp_ctx = conn->rtp.mgcp_ctx;
- if (!mgcp_ctx)
- return -EINVAL;
-
- LOGPFSMSL(conn->fi, DMGCP, LOGL_ERROR, "Assignment failed\n");
-
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN_ERROR, mgcp_ctx);
- return 0;
-}
-
-/* Make the connection of a previously assigned call complete
- * Parameter:
- * trans: transaction context.
- * port: port number of the remote leg.
- * addr: IP-address of the remote leg.
- * Returns -EINVAL on error, 0 on success. */
-int msc_mgcp_call_complete(struct gsm_trans *trans, uint16_t port, char *addr)
-{
- struct mgcp_ctx *mgcp_ctx;
- struct ran_conn *conn;
-
- OSMO_ASSERT(trans);
- OSMO_ASSERT(addr);
-
- if (port == 0) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid remote call leg port, call completion failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
- if (!addr || strlen(addr) <= 0) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) missing remote call leg address, call completion failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
- if (!trans->conn) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid conn, call completion failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
- if (!trans->conn->rtp.mgcp_ctx) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid mgcp context, call completion failed.\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
- if (!trans->conn->rtp.mgcp_ctx->fsm) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) no FSM, call completion failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
-
- mgcp_ctx = trans->conn->rtp.mgcp_ctx;
-
- /* The FSM should already have passed all CRCX phases and be ready to move
- * on with the MDCX phases. */
- if (mgcp_ctx->fsm->state != ST_MDCX_CN) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid call state, call completion failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
-
- conn = trans->conn;
- osmo_strlcpy(conn->rtp.remote_addr_cn, addr, sizeof(conn->rtp.remote_addr_cn));
- conn->rtp.remote_port_cn = port;
-
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CONNECT, mgcp_ctx);
-
- LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) call completion initiated\n",
- vlr_subscr_name(conn->vsub));
-
- return 0;
-}
-
-static struct gsm_trans *find_waiting_call(struct ran_conn *conn)
-{
- struct gsm_trans *trans;
- struct gsm_network *net = conn->network;
-
- llist_for_each_entry(trans, &net->trans_list, entry) {
- if (trans->conn != conn)
- continue;
- if (trans->protocol != GSM48_PDISC_CC)
- continue;
- if (trans->cc.assignment_started)
- continue;
- return trans;
- }
- return NULL;
-}
-
-/* Release ongoing call.
- * Parameter:
- * trans: connection context.
- * Returns -EINVAL on error, 0 on success. */
-int msc_mgcp_call_release(struct gsm_trans *trans)
-{
- struct mgcp_ctx *mgcp_ctx;
- struct ran_conn *conn = trans->conn;
- struct gsm_trans *waiting_trans;
-
- OSMO_ASSERT(trans);
-
- if (!conn) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid conn, call release failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
- mgcp_ctx = conn->rtp.mgcp_ctx;
- if (!mgcp_ctx) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid mgcp context, call release failed.\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
- if (!mgcp_ctx->fsm) {
- LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) no FSM, call release failed\n",
- vlr_subscr_name(trans->vsub));
- return -EINVAL;
- }
-
- if (mgcp_ctx->trans != trans) {
- LOGP(DMGCP, LOGL_DEBUG, "(ti %02x %s) call release for background CC transaction\n",
- trans->transaction_id, vlr_subscr_name(trans->vsub));
- return 0;
- }
-
- LOGP(DMGCP, LOGL_DEBUG, "(ti %02x %s) Call release: tearing down MGW endpoint\n",
- trans->transaction_id, vlr_subscr_name(trans->vsub));
-
- /* Inform the FSM that as soon as it reaches ST_HALT it may free
- * all context information immediately */
- mgcp_ctx->free_ctx = true;
-
- /* Initaite teardown, regardless of which state we are currently
- * in */
- osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
-
- /* Prevent any further operation that is triggered from outside by
- * overwriting the context pointer with NULL. The FSM will now
- * take care for a graceful shutdown and when done it will free
- * all related context information */
- conn->rtp.mgcp_ctx = NULL;
-
- /* If there is another call still waiting to be activated, this is the time when the mgcp_ctx is available again
- * and the other call can start assigning. */
- waiting_trans = find_waiting_call(conn);
- if (waiting_trans) {
- LOGP(DMGCP, LOGL_DEBUG, "(ti %02x %s) Call waiting: starting Assignment\n",
- waiting_trans->transaction_id, vlr_subscr_name(trans->vsub));
- msc_mgcp_try_call_assignment(waiting_trans);
- }
-
- return 0;
-}
diff --git a/src/libmsc/msc_net_init.c b/src/libmsc/msc_net_init.c
new file mode 100644
index 000000000..51e859595
--- /dev/null
+++ b/src/libmsc/msc_net_init.c
@@ -0,0 +1,126 @@
+/* main MSC management code... */
+
+/*
+ * (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "bscconfig.h"
+
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/gsup_client_mux.h>
+#include <osmocom/msc/gsm_04_11_gsup.h>
+#include <osmocom/msc/gsm_09_11.h>
+
+struct osmo_tdef mncc_tdefs[] = {
+ {}
+};
+
+struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv)
+{
+ struct gsm_network *net;
+
+ net = talloc_zero(ctx, struct gsm_network);
+ if (!net)
+ return NULL;
+
+ net->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
+
+ /* Permit a compile-time default of A5/3 and A5/1 */
+ net->a5_encryption_mask = (1 << 3) | (1 << 1);
+
+ /* Use 30 min periodic update interval as sane default */
+ net->t3212 = 5;
+
+ net->mncc_guard_timeout = 180;
+ net->ncss_guard_timeout = 30;
+
+ net->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT;
+
+ INIT_LLIST_HEAD(&net->trans_list);
+ INIT_LLIST_HEAD(&net->upqueue);
+ INIT_LLIST_HEAD(&net->neighbor_ident_list);
+
+ /* init statistics */
+ net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
+ if (!net->msc_ctrs) {
+ talloc_free(net);
+ return NULL;
+ }
+ net->active_calls = osmo_counter_alloc("msc.active_calls");
+ net->active_nc_ss = osmo_counter_alloc("msc.active_nc_ss");
+
+ net->mncc_tdefs = mncc_tdefs;
+ net->mncc_recv = mncc_recv;
+
+ return net;
+}
+
+void gsm_network_set_mncc_sock_path(struct gsm_network *net, const char *mncc_sock_path)
+{
+ if (net->mncc_sock_path)
+ talloc_free(net->mncc_sock_path);
+ net->mncc_sock_path = mncc_sock_path ? talloc_strdup(net, mncc_sock_path) : NULL;
+}
+
+/* Allocate net->vlr so that the VTY may configure the VLR's data structures */
+int msc_vlr_alloc(struct gsm_network *net)
+{
+ net->vlr = vlr_alloc(net, &msc_vlr_ops);
+ if (!net->vlr)
+ return -ENOMEM;
+ net->vlr->user_ctx = net;
+ return 0;
+}
+
+/* Launch the VLR, i.e. its GSUP connection */
+int msc_vlr_start(struct gsm_network *net)
+{
+ OSMO_ASSERT(net->vlr);
+ OSMO_ASSERT(net->gcm);
+
+ return vlr_start(net->vlr, net->gcm);
+}
+
+int msc_gsup_client_start(struct gsm_network *net)
+{
+ struct ipaccess_unit *ipa_dev;
+
+ net->gcm = gsup_client_mux_alloc(net);
+ OSMO_ASSERT(net->gcm);
+
+ ipa_dev = talloc_zero(net->gcm, struct ipaccess_unit);
+ ipa_dev->unit_name = "MSC";
+ ipa_dev->serno = net->msc_ipa_name; /* NULL unless configured via VTY */
+ ipa_dev->swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
+
+ *net->gcm = (struct gsup_client_mux){
+ .rx_cb = {
+ /* vlr.c sets up its own cb and data */
+ /* MSC-A and MSC-B set up their own cb and data */
+ [OSMO_GSUP_MESSAGE_CLASS_SMS] = { .func = gsm411_gsup_rx, .data = net->vlr },
+ [OSMO_GSUP_MESSAGE_CLASS_USSD] = { .func = gsm0911_gsup_rx, .data = net->vlr },
+ },
+ };
+
+ return gsup_client_mux_start(net->gcm, net->gsup_server_addr_str, net->gsup_server_port, ipa_dev);
+}
diff --git a/src/libmsc/msc_t.c b/src/libmsc/msc_t.c
new file mode 100644
index 000000000..ec5531fc2
--- /dev/null
+++ b/src/libmsc/msc_t.c
@@ -0,0 +1,962 @@
+/* The MSC-T role, a transitional RAN connection during Handover. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <inttypes.h>
+
+#include <osmocom/gsm/gsm48_ie.h>
+
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_a_remote.h>
+#include <osmocom/msc/ran_infra.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/ran_conn.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/ran_infra.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/gsm_data.h>
+
+static struct osmo_fsm msc_t_fsm;
+
+static struct msc_t *msc_t_find_by_handover_number(const char *handover_number)
+{
+ struct msub *msub;
+
+ llist_for_each_entry(msub, &msub_list, entry) {
+ struct msc_t *msc_t = msub_msc_t(msub);
+ if (!msc_t)
+ continue;
+ if (!*msc_t->inter_msc.handover_number)
+ continue;
+ if (strcmp(msc_t->inter_msc.handover_number, handover_number))
+ continue;
+ /* Found the assigned Handover Number */
+ return msc_t;
+ }
+ return NULL;
+}
+
+static uint64_t net_handover_number_next(struct gsm_network *net)
+{
+ uint64_t nr;
+ if (net->handover_number.next < net->handover_number.range_start
+ || net->handover_number.next > net->handover_number.range_end)
+ net->handover_number.next = net->handover_number.range_start;
+ nr = net->handover_number.next;
+ net->handover_number.next++;
+ return nr;
+}
+
+static int msc_t_assign_handover_number(struct msc_t *msc_t)
+{
+ int rc;
+ uint64_t started_at;
+ uint64_t ho_nr;
+ char ho_nr_str[VLR_MSISDN_LENGTH+1];
+ struct gsm_network *net = msc_t_net(msc_t);
+ bool usable = false;
+
+ started_at = ho_nr = net_handover_number_next(net);
+
+ if (!ho_nr) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "No Handover Number range defined in MSC config\n");
+ return -ENOENT;
+ }
+
+ do {
+ rc = snprintf(ho_nr_str, sizeof(ho_nr_str), "%"PRIu64, ho_nr);
+ if (rc <= 0 || rc >= sizeof(ho_nr_str)) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Cannot compose Handover Number string (rc=%d)\n", rc);
+ return -EINVAL;
+ }
+
+ if (!msc_t_find_by_handover_number(ho_nr_str)) {
+ usable = true;
+ break;
+ }
+
+ ho_nr = net_handover_number_next(net);
+ } while(ho_nr != started_at);
+
+ if (!usable) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "No Handover Number available\n");
+ return -EINVAL;
+ }
+
+ LOG_MSC_T(msc_t, LOGL_INFO, "Assigning Handover Number %s\n", ho_nr_str);
+ OSMO_STRLCPY_ARRAY(msc_t->inter_msc.handover_number, ho_nr_str);
+ return 0;
+}
+
+
+static struct msc_t *msc_t_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &msc_t_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+/* As a macro to log the caller's source file and line.
+ * Assumes presence of local msc_t variable. */
+#define msc_t_error(fmt, args...) do { \
+ msc_t->ho_success = false; \
+ LOG_MSC_T(msc_t, LOGL_ERROR, fmt, ##args); \
+ msc_t_clear(msc_t); \
+ } while(0)
+
+static void msc_t_send_handover_failure(struct msc_t *msc_t, enum gsm0808_cause cause)
+{
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_FAILURE,
+ .handover_failure = {
+ .cause = cause,
+ },
+ };
+ struct an_apdu an_apdu = {
+ .an_proto = msc_t->c.ran->an_proto,
+ .msg = msc_role_ran_encode(msc_t->c.fi, &ran_enc_msg),
+ };
+ msc_t->ho_fail_sent = true;
+ if (!an_apdu.msg)
+ return;
+
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, &an_apdu);
+ msgb_free(an_apdu.msg);
+}
+
+static int msc_t_ho_request_decode_and_store_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
+ const struct ran_msg *ran_dec)
+{
+ struct msc_t *msc_t = msc_t_priv(msc_t_fi);
+
+ if (ran_dec->msg_type != RAN_MSG_HANDOVER_REQUEST) {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "Expected %s in incoming inter-MSC Handover message, got %s\n",
+ ran_msg_type_name(RAN_MSG_HANDOVER_REQUEST), ran_msg_type_name(ran_dec->msg_type));
+ return -EINVAL;
+ }
+
+ msc_t->inter_msc.cell_id_target = ran_dec->handover_request.cell_id_target;
+ msc_t->inter_msc.callref = ran_dec->handover_request.call_id;
+
+ /* TODO other parameters...?
+ * Global Call Reference
+ */
+ return 0;
+}
+
+/* On an icoming Handover Request from a remote MSC, we first need to set up an MGW endpoint, because the BSC needs to
+ * know our AoIP Transport Layer Address in the Handover Request message (which obviously the remote MSC doesn't send,
+ * it needs to be our local RTP address). Creating the MGW endpoint this is asynchronous, so we need to store the
+ * Handover Request data to forward to the BSC once the MGW endpoint is known.
+ */
+static int msc_t_decode_and_store_ho_request(struct msc_t *msc_t, const struct an_apdu *an_apdu)
+{
+ if (msc_role_ran_decode(msc_t->c.fi, an_apdu, msc_t_ho_request_decode_and_store_cb, NULL)) {
+ msc_t_error("Failed to decode Handover Request\n");
+ return -ENOTSUP;
+ }
+ /* Ok, decoding done, and above msc_t_ho_request_decode_and_store_cb() has retrieved what info we need at this
+ * point and stored it in msc_t->inter_msc.* */
+
+ /* We're storing this for use after async events, so need to make sure that each and every bit of data is copied
+ * and no longer references some msgb that might be deallocated when this returns, nor remains in a local stack
+ * variable of some ran_decode implementation. The simplest is to store the entire msgb. */
+ msc_t->inter_msc.ho_request = (struct an_apdu) {
+ .an_proto = an_apdu->an_proto,
+ .msg = msgb_copy(an_apdu->msg, "saved inter-MSC Handover Request"),
+ /* A decoded osmo_gsup_message often still references memory of within the msgb the GSUP was received
+ * in. So, any info from an_apdu->e_info that would be needed would have to be copied separately.
+ * Omit e_info completely. */
+ };
+ return 0;
+}
+
+/* On an incoming Handover Request from a remote MSC, the target cell was transmitted in the Handover Request message.
+ * Find the RAN peer and assign from the cell id decoded above in msc_t_decode_and_store_ho_request(). */
+static int msc_t_find_ran_peer_from_ho_request(struct msc_t *msc_t)
+{
+ struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
+ const struct neighbor_ident_entry *nie;
+ struct ran_peer *rp_from_neighbor_ident;
+ struct ran_peer *rp;
+
+ switch (msc_ho_find_target_cell(msc_a, &msc_t->inter_msc.cell_id_target,
+ &nie, &rp_from_neighbor_ident, &rp)) {
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ msc_t_error("Incoming Handover Request indicated target cell that belongs to a remote MSC:"
+ " Cell ID: %s; remote MSC: %s\n",
+ gsm0808_cell_id_name(&msc_t->inter_msc.cell_id_target),
+ neighbor_ident_addr_name(&nie->addr));
+ return -EINVAL;
+
+ case MSC_NEIGHBOR_TYPE_NONE:
+ msc_t_error("Incoming Handover Request for unknown cell %s\n",
+ gsm0808_cell_id_name(&msc_t->inter_msc.cell_id_target));
+ return -EINVAL;
+
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ /* That's what is expected: a local RAN peer, e.g. BSC, or a remote BSC from neighbor cfg. */
+ if (!rp)
+ rp = rp_from_neighbor_ident;
+ break;
+ }
+
+ OSMO_ASSERT(rp);
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "Incoming Handover Request indicates target cell %s,"
+ " which belongs to RAN peer %s\n",
+ gsm0808_cell_id_name(&msc_t->inter_msc.cell_id_target), rp->fi->id);
+
+ /* Finally we know where to direct the Handover */
+ msc_t_set_ran_peer(msc_t, rp);
+ return 0;
+}
+
+static int msc_t_send_stored_ho_request__decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
+ const struct ran_msg *ran_dec)
+{
+ int rc;
+ struct an_apdu an_apdu;
+ struct msc_t *msc_t = msc_t_priv(msc_t_fi);
+ struct osmo_sockaddr_str *rtp_ran_local = data;
+
+ /* Copy ran_dec message to un-const so we can add the AoIP Transport Layer Address. All pointer references still
+ * remain on the same memory as ran_dec, which is fine. We're just going to encode it again right away. */
+ struct ran_msg ran_enc = *ran_dec;
+
+ if (ran_dec->msg_type != RAN_MSG_HANDOVER_REQUEST) {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "Expected %s in incoming inter-MSC Handover message, got %s\n",
+ ran_msg_type_name(RAN_MSG_HANDOVER_REQUEST), ran_msg_type_name(ran_dec->msg_type));
+ return -EINVAL;
+ }
+
+ /* Insert AoIP Transport Layer Address */
+ ran_enc.handover_request.rtp_ran_local = rtp_ran_local;
+
+ /* Finally ready to forward to BSC: encode and send out. */
+ an_apdu = (struct an_apdu){
+ .an_proto = msc_t->inter_msc.ho_request.an_proto,
+ .msg = msc_role_ran_encode(msc_t->c.fi, &ran_enc),
+ };
+ if (!an_apdu.msg)
+ return -EIO;
+ rc = msc_t_down_l2_co(msc_t, &an_apdu, true);
+ msgb_free(an_apdu.msg);
+ return rc;
+}
+
+/* The MGW endpoint is created, we know our AoIP Transport Layer Address and can send the Handover Request to the RAN
+ * peer. */
+static int msc_t_send_stored_ho_request(struct msc_t *msc_t)
+{
+ struct osmo_sockaddr_str *rtp_ran_local = call_leg_local_ip(msc_t->inter_msc.call_leg, RTP_TO_RAN);
+ if (!rtp_ran_local) {
+ msc_t_error("Local RTP address towards RAN is not set up properly, cannot send Handover Request\n");
+ return -EINVAL;
+ }
+
+ /* The Handover Request received from the remote MSC is fed through, except we need to insert our local AoIP
+ * Transport Layer Address, i.e. the RTP IP:port of the MGW towards the RAN side. So we actually need to decode,
+ * add the AoIP and re-encode. By nature of decoding, it goes through the decode callback. */
+ return msc_role_ran_decode(msc_t->c.fi, &msc_t->inter_msc.ho_request,
+ msc_t_send_stored_ho_request__decode_cb, rtp_ran_local);
+}
+
+static void msc_t_fsm_pending_first_co_initial_msg(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_t *msc_t = msc_t_priv(fi);
+ struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
+ struct an_apdu *an_apdu;
+
+ OSMO_ASSERT(msc_a);
+
+ switch (event) {
+
+ case MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST:
+ /* For an inter-MSC Handover coming in from a remote MSC, we do not yet know the RAN peer and AoIP
+ * Transport Layer Address.
+ * - RAN peer is found by decoding the actual Handover Request message and looking for the Cell
+ * Identifier (Target).
+ * - To be able to tell the BSC about an AoIP Transport Layer Address, we first need to create an MGW
+ * endpoint.
+ * For mere inter-BSC Handover, we know all of the above already. Find out which one this is.
+ */
+ an_apdu = data;
+ if (!msc_a->c.remote_to) {
+ /* Inter-BSC */
+
+ osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_REQUEST_ACK, 0, 0);
+ /* Inter-BSC. All should be set up, just forward the message. */
+ if (msc_t_down_l2_co(msc_t, an_apdu, true))
+ msc_t_error("Failed to send AN-APDU to RAN peer\n");
+ } else {
+ /* Inter-MSC */
+
+ if (msc_t->ran_conn) {
+ msc_t_error("Unexpected state for inter-MSC Handover: RAN peer is already set up\n");
+ return;
+ }
+
+ if (msc_t_decode_and_store_ho_request(msc_t, an_apdu))
+ return;
+
+ if (msc_t_find_ran_peer_from_ho_request(msc_t))
+ return;
+
+ /* Relying on timeout of the MGW operations, see onenter() for this state. */
+ osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_LOCAL_RTP, 0, 0);
+ }
+ return;
+
+ case MSC_T_EV_CN_CLOSE:
+ msc_t_clear(msc_t);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+void msc_t_fsm_wait_local_rtp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_t *msc_t = msc_t_priv(fi);
+ struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
+
+ /* This only happens on inter-MSC HO incoming from a remote MSC */
+ if (!msc_a->c.remote_to) {
+ msc_t_error("Unexpected state: this is not an inter-MSC Handover\n");
+ return;
+ }
+
+ if (msc_t->inter_msc.call_leg) {
+ msc_t_error("Unexpected state: call leg already set up\n");
+ return;
+ }
+
+ msc_t->inter_msc.call_leg = call_leg_alloc(msc_t->c.fi,
+ MSC_EV_CALL_LEG_TERM,
+ MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
+ MSC_EV_CALL_LEG_RTP_COMPLETE,
+ MSC_EV_CALL_LEG_RTP_RELEASED);
+ if (!msc_t->inter_msc.call_leg
+ || call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_RAN, msc_t->inter_msc.callref, NULL, NULL, NULL)
+ || call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_CN, msc_t->inter_msc.callref, NULL, NULL, NULL)) {
+ msc_t_error("Failed to set up call leg\n");
+ return;
+ }
+ /* Now wait for two MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, one per RTP connection */
+}
+
+void msc_t_fsm_wait_local_rtp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_t *msc_t = msc_t_priv(fi);
+ struct rtp_stream *rtps;
+
+ switch (event) {
+ case MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE:
+ rtps = data;
+ if (!rtps) {
+ msc_t_error("Invalid data for MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE\n");
+ return;
+ }
+ /* If both to-RAN and to-CN sides have a CI set up, we can continue. */
+ if (!call_leg_local_ip(msc_t->inter_msc.call_leg, RTP_TO_RAN)
+ || !call_leg_local_ip(msc_t->inter_msc.call_leg, RTP_TO_CN))
+ return;
+
+ osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_REQUEST_ACK, 0, 0);
+ msc_t_send_stored_ho_request(msc_t);
+ return;
+
+ case MSC_EV_CALL_LEG_RTP_RELEASED:
+ case MSC_EV_CALL_LEG_TERM:
+ msc_t->inter_msc.call_leg = NULL;
+ msc_t_error("Failed to set up MGW endpoint\n");
+ return;
+
+ case MSC_MNCC_EV_CALL_ENDED:
+ msc_t->inter_msc.mncc_forwarding_to_remote_cn = NULL;
+ return;
+
+ case MSC_T_EV_CN_CLOSE:
+ case MSC_T_EV_MO_CLOSE:
+ msc_t_clear(msc_t);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int msc_t_patch_and_send_ho_request_ack(struct msc_t *msc_t, const struct an_apdu *incoming_an_apdu,
+ const struct ran_msg *ran_dec)
+{
+ int rc;
+ struct rtp_stream *rtp_ran = msc_t->inter_msc.call_leg? msc_t->inter_msc.call_leg->rtp[RTP_TO_RAN] : NULL;
+ struct rtp_stream *rtp_cn = msc_t->inter_msc.call_leg? msc_t->inter_msc.call_leg->rtp[RTP_TO_CN] : NULL;
+ /* Since it's BCD, it needs rounded-up half the char* length of an MSISDN plus a type byte.
+ * But no need to introduce obscure math to save a few stack bytes, just have more. */
+ uint8_t msisdn_enc_buf[VLR_MSISDN_LENGTH + 1];
+ /* Copy an_apdu and an_apdu->e_info in "copy-on-write" method, because they are const and we
+ * need to add the Handover Number to e_info. */
+ const struct ran_handover_request_ack *r = &ran_dec->handover_request_ack;
+ struct ran_msg ran_enc = *ran_dec;
+ struct osmo_gsup_message e_info = {};
+ struct an_apdu an_apdu = {
+ .an_proto = incoming_an_apdu->an_proto,
+ .e_info = &e_info,
+ };
+ if (incoming_an_apdu->e_info)
+ e_info = *incoming_an_apdu->e_info;
+
+ rc = msc_t_assign_handover_number(msc_t);
+ if (rc)
+ return rc;
+
+ rc = gsm48_encode_bcd_number(msisdn_enc_buf, sizeof(msisdn_enc_buf), 0,
+ msc_t->inter_msc.handover_number);
+ if (rc <= 0)
+ return -EINVAL;
+
+ e_info.msisdn_enc = msisdn_enc_buf;
+ e_info.msisdn_enc_len = rc;
+
+ /* Also need to fetch the RTP IP:port from AoIP Transport Address IE to tell the MGW about it */
+ if (rtp_ran) {
+ if (osmo_sockaddr_str_is_set(&r->remote_rtp)) {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&r->remote_rtp));
+ rtp_stream_set_remote_addr(rtp_ran, &r->remote_rtp);
+ } else {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "No RTP IP:port in Handover Request Ack\n");
+ }
+ if (r->codec_present) {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got %s\n",
+ osmo_mgcpc_codec_name(r->codec));
+ rtp_stream_set_codec(rtp_ran, r->codec);
+ if (rtp_cn)
+ rtp_stream_set_codec(rtp_cn, r->codec);
+ } else {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "No codec in Handover Request Ack\n");
+ }
+ rtp_stream_commit(rtp_ran);
+ } else {
+ LOG_MSC_T(msc_t, LOGL_DEBUG, "No RTP to RAN set up yet\n");
+ }
+
+ /* Remove that AoIP Transport Layer IE so it doesn't get sent to the remote MSC */
+ ran_enc.handover_request_ack.remote_rtp = (struct osmo_sockaddr_str){};
+
+ an_apdu.msg = msc_role_ran_encode(msc_t->c.fi, &ran_enc);
+ if (!an_apdu.msg)
+ return -EIO;
+ /* Send to remote MSC via msc_a_remote role */
+ rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE, &an_apdu);
+ msgb_free(an_apdu.msg);
+ return rc;
+}
+
+static int msc_t_wait_ho_request_ack_decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
+ const struct ran_msg *ran_dec)
+{
+ int rc;
+ struct msc_t *msc_t = msc_t_priv(msc_t_fi);
+ struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
+ const struct an_apdu *an_apdu = data;
+
+ switch (ran_dec->msg_type) {
+ case RAN_MSG_HANDOVER_REQUEST_ACK:
+ if (msc_a->c.remote_to) {
+ /* inter-MSC. Add Handover Number, remove AoIP Transport Layer Address. */
+ rc = msc_t_patch_and_send_ho_request_ack(msc_t, an_apdu, ran_dec);
+ } else {
+ /* inter-BSC. Just send as-is, with correct event. */
+ rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE,
+ an_apdu);
+ }
+ if (rc)
+ msc_t_error("Failed to send HO Request Ack\n");
+ else
+ osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_COMPLETE, 0, 0);
+ return 0;
+
+ case RAN_MSG_HANDOVER_FAILURE:
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, an_apdu);
+ return 0;
+
+ case RAN_MSG_CLEAR_REQUEST:
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
+ an_apdu);
+ return 0;
+
+ default:
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Unexpected message during Prepare Handover procedure: %s\n",
+ ran_msg_type_name(ran_dec->msg_type));
+ /* Let's just forward anyway. */
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
+ an_apdu);
+ return 0;
+ }
+}
+
+static void msc_t_fsm_wait_ho_request_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_t *msc_t = msc_t_priv(fi);
+ struct an_apdu *an_apdu;
+
+ switch (event) {
+
+ case MSC_EV_FROM_RAN_UP_L2:
+ an_apdu = data;
+ /* For inter-MSC Handover, we need to examine the message type. Depending on the response, we must
+ * dispatch MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE or MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, which
+ * ensures the correct E-interface message type. And we need to include the Handover Number.
+ * For mere inter-BSC Handover, we know that our osmo-msc internals don't care much about which event
+ * dispatches a Handover Failure or Handover Request Ack, so we could skip the decoding. But it is a
+ * premature optimization that complicates comparing an inter-BSC with an inter-MSC HO. */
+ msc_role_ran_decode(msc_t->c.fi, an_apdu, msc_t_wait_ho_request_ack_decode_cb, an_apdu);
+ /* Action continues in msc_t_wait_ho_request_ack_decode_cb() */
+ return;
+
+ case MSC_EV_FROM_RAN_CONN_RELEASED:
+ msc_t_clear(msc_t);
+ return;
+
+ case MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ an_apdu = data;
+ msc_t_down_l2_co(msc_t, an_apdu, false);
+ return;
+
+ case MSC_EV_CALL_LEG_RTP_RELEASED:
+ case MSC_EV_CALL_LEG_TERM:
+ msc_t->inter_msc.call_leg = NULL;
+ msc_t_error("Failed to set up MGW endpoint\n");
+ return;
+
+ case MSC_MNCC_EV_CALL_ENDED:
+ msc_t->inter_msc.mncc_forwarding_to_remote_cn = NULL;
+ return;
+
+ case MSC_T_EV_CN_CLOSE:
+ case MSC_T_EV_MO_CLOSE:
+ msc_t_clear(msc_t);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int msc_t_wait_ho_complete_decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
+ const struct ran_msg *ran_dec)
+{
+ struct msc_t *msc_t = msc_t_priv(msc_t_fi);
+ struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
+ struct msc_i *msc_i;
+ const struct an_apdu *an_apdu = data;
+
+ switch (ran_dec->msg_type) {
+ case RAN_MSG_HANDOVER_COMPLETE:
+ msc_t->ho_success = true;
+
+ /* For both inter-BSC local to this MSC and inter-MSC Handover for a remote MSC-A, forward the Handover
+ * Complete message so that the MSC-A can change the MSC-T (transitional) to a proper MSC-I role. */
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST, an_apdu);
+
+ /* For inter-BSC Handover, the Handover Complete event has already cleaned up this msc_t, and it is
+ * already gone and deallocated. */
+ if (!msc_a->c.remote_to)
+ return 0;
+
+ /* For inter-MSC Handover, the remote MSC-A only turns its msc_t_remote into an msc_i_remote on
+ * the same GSUP link. We are here on the MSC-B side of the GSUP link and have to take care of
+ * creating an MSC-I over here to match the msc_i_remote at MSC-A. */
+ msc_i = msc_i_alloc(msc_t->c.msub, msc_t->c.ran);
+ if (!msc_i) {
+ msc_t_error("Failed to create MSC-I role\n");
+ return -1;
+ }
+
+ msc_i->inter_msc.mncc_forwarding_to_remote_cn = msc_t->inter_msc.mncc_forwarding_to_remote_cn;
+ mncc_call_reparent(msc_i->inter_msc.mncc_forwarding_to_remote_cn,
+ msc_i->c.fi, -1, MSC_MNCC_EV_CALL_ENDED, NULL, NULL);
+
+ msc_i->inter_msc.call_leg = msc_t->inter_msc.call_leg;
+ call_leg_reparent(msc_i->inter_msc.call_leg,
+ msc_i->c.fi,
+ MSC_EV_CALL_LEG_TERM,
+ MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
+ MSC_EV_CALL_LEG_RTP_COMPLETE,
+ MSC_EV_CALL_LEG_RTP_RELEASED);
+
+ /* msc_i_set_ran_conn() properly "steals" the ran_conn from msc_t */
+ msc_i_set_ran_conn(msc_i, msc_t->ran_conn);
+
+ /* Nicked everything worth keeping from MSC-T, discard now. */
+ msc_t_clear(msc_t);
+ return 0;
+
+ case RAN_MSG_HANDOVER_FAILURE:
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, an_apdu);
+ return 0;
+
+ default:
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Unexpected message during Prepare Handover procedure: %s\n",
+ ran_msg_type_name(ran_dec->msg_type));
+ /* Let's just forward anyway. Fall thru */
+ case RAN_MSG_HANDOVER_DETECT:
+ case RAN_MSG_CLEAR_REQUEST:
+ msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
+ an_apdu);
+ return 0;
+ }
+}
+
+static void msc_t_fsm_wait_ho_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_t *msc_t = msc_t_priv(fi);
+ struct an_apdu *an_apdu;
+
+ switch (event) {
+
+ case MSC_EV_FROM_RAN_UP_L2:
+ an_apdu = data;
+ /* We need to catch the Handover Complete message in order to send it as a SendEndSignal Request */
+ msc_role_ran_decode(msc_t->c.fi, an_apdu, msc_t_wait_ho_complete_decode_cb, an_apdu);
+ return;
+
+ case MSC_EV_FROM_RAN_CONN_RELEASED:
+ msc_t_clear(msc_t);
+ return;
+
+ case MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ an_apdu = data;
+ msc_t_down_l2_co(msc_t, an_apdu, false);
+ return;
+
+ case MSC_EV_CALL_LEG_RTP_RELEASED:
+ case MSC_EV_CALL_LEG_TERM:
+ msc_t->inter_msc.call_leg = NULL;
+ msc_t_error("Failed to set up MGW endpoint\n");
+ return;
+
+ case MSC_MNCC_EV_CALL_ENDED:
+ msc_t->inter_msc.mncc_forwarding_to_remote_cn = NULL;
+ return;
+
+ case MSC_T_EV_CN_CLOSE:
+ case MSC_T_EV_MO_CLOSE:
+ msc_t_clear(msc_t);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+void msc_t_mncc_cb(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg, void *data)
+{
+ struct msc_t *msc_t = data;
+ struct gsm_mncc_number nr = {
+ .plan = 1,
+ };
+ OSMO_STRLCPY_ARRAY(nr.number, msc_t->inter_msc.handover_number);
+
+ switch (mncc_msg->msg_type) {
+ case MNCC_RTP_CREATE:
+ mncc_call_incoming_tx_setup_cnf(mncc_call, &nr);
+ return;
+ default:
+ return;
+ }
+}
+
+struct mncc_call *msc_t_check_call_to_handover_number(const struct gsm_mncc *msg)
+{
+ struct msc_t *msc_t;
+ const char *handover_number;
+ struct mncc_call_incoming_req req;
+ struct mncc_call *mncc_call;
+
+ if (!(msg->fields & MNCC_F_CALLED))
+ return NULL;
+
+ handover_number = msg->called.number;
+ msc_t = msc_t_find_by_handover_number(handover_number);
+
+ if (!msc_t)
+ return NULL;
+
+ if (msc_t->inter_msc.mncc_forwarding_to_remote_cn) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Incoming call for inter-MSC call forwarding,"
+ " but this MSC-T role already has an MNCC FSM set up\n");
+ return NULL;
+ }
+
+ if (!msc_t->inter_msc.call_leg
+ || !msc_t->inter_msc.call_leg->rtp[RTP_TO_CN]) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Incoming call for inter-MSC call forwarding,"
+ " but this MSC-T has no RTP stream ready for MNCC\n");
+ return NULL;
+ }
+
+ mncc_call = mncc_call_alloc(msc_t_vsub(msc_t),
+ msc_t->c.fi,
+ MSC_MNCC_EV_CALL_COMPLETE,
+ MSC_MNCC_EV_CALL_ENDED,
+ msc_t_mncc_cb, msc_t);
+ if (!mncc_call) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to set up call forwarding from remote MSC\n");
+ return NULL;
+ }
+ msc_t->inter_msc.mncc_forwarding_to_remote_cn = mncc_call;
+
+ if (mncc_call_set_rtp_stream(mncc_call, msc_t->inter_msc.call_leg->rtp[RTP_TO_CN])) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to set up call forwarding from remote MSC\n");
+ osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return NULL;
+ }
+
+ req = (struct mncc_call_incoming_req){
+ .setup_req_msg = *msg,
+ .bearer_cap_present = true,
+ .bearer_cap = {
+ /* TODO derive values from actual config */
+ /* FIXME are there no defines or enums for these numbers!? */
+ /* Table 10.5.102/3GPP TS 24.008: Bearer capability information element:
+ * octet 3 of bearer cap for speech says 3 = "1 1 dual rate support MS/full rate speech version
+ * 1 preferred, half rate speech version 1 also supported" */
+ .radio = 3,
+ /* Table 10.5.103/3GPP TS 24.008 Bearer capability information element:
+ * 0: FR1, 2: FR2, 4: FR3, 1: HR1, 5: HR3, actually in this order. -1 marks the end of the list. */
+ .speech_ver = { 0, 2, 4, 1, 5, -1 },
+ },
+ };
+ if (mncc_call_incoming_start(mncc_call, &req)) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to set up call forwarding from remote MSC\n");
+ osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return NULL;
+ }
+ return mncc_call;
+}
+
+static void msc_t_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_t *msc_t = msc_t_priv(fi);
+
+ if (!msc_t->ho_success && !msc_t->ho_fail_sent)
+ msc_t_send_handover_failure(msc_t, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+
+ if (msc_t->ran_conn)
+ ran_conn_msc_role_gone(msc_t->ran_conn, msc_t->c.fi);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_t_fsm_states[] = {
+ [MSC_T_ST_PENDING_FIRST_CO_INITIAL_MSG] = {
+ .name = "PENDING_FIRST_CO_INITIAL_MSG",
+ .action = msc_t_fsm_pending_first_co_initial_msg,
+ .in_event_mask = 0
+ | S(MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST)
+ | S(MSC_T_EV_CN_CLOSE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_T_ST_WAIT_LOCAL_RTP)
+ | S(MSC_T_ST_WAIT_HO_REQUEST_ACK)
+ ,
+ },
+ [MSC_T_ST_WAIT_LOCAL_RTP] = {
+ .name = "WAIT_LOCAL_RTP",
+ .onenter = msc_t_fsm_wait_local_rtp_onenter,
+ .action = msc_t_fsm_wait_local_rtp,
+ .in_event_mask = 0
+ | S(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE)
+ | S(MSC_EV_CALL_LEG_RTP_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ | S(MSC_T_EV_CN_CLOSE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_T_ST_WAIT_HO_REQUEST_ACK)
+ ,
+ },
+ [MSC_T_ST_WAIT_HO_REQUEST_ACK] = {
+ .name = "WAIT_HO_REQUEST_ACK",
+ .action = msc_t_fsm_wait_ho_request_ack,
+ .in_event_mask = 0
+ | S(MSC_EV_FROM_RAN_UP_L2)
+ | S(MSC_EV_FROM_RAN_CONN_RELEASED)
+ | S(MSC_EV_CALL_LEG_RTP_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ | S(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_T_EV_CN_CLOSE)
+ | S(MSC_T_EV_MO_CLOSE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_T_ST_WAIT_HO_COMPLETE)
+ ,
+ },
+ [MSC_T_ST_WAIT_HO_COMPLETE] = {
+ .name = "WAIT_HO_COMPLETE",
+ .action = msc_t_fsm_wait_ho_complete,
+ .in_event_mask = 0
+ | S(MSC_EV_FROM_RAN_UP_L2)
+ | S(MSC_EV_FROM_RAN_CONN_RELEASED)
+ | S(MSC_EV_CALL_LEG_RTP_RELEASED)
+ | S(MSC_EV_CALL_LEG_TERM)
+ | S(MSC_MNCC_EV_CALL_ENDED)
+ | S(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
+ | S(MSC_T_EV_CN_CLOSE)
+ | S(MSC_T_EV_MO_CLOSE)
+ ,
+ },
+};
+
+const struct value_string msc_t_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MSC_REMOTE_EV_RX_GSUP),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_COMPLETE),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_RELEASED),
+ OSMO_VALUE_STRING(MSC_EV_CALL_LEG_TERM),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_NEED_LOCAL_RTP),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_PROCEEDING),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_COMPLETE),
+ OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_ENDED),
+
+ OSMO_VALUE_STRING(MSC_EV_FROM_RAN_COMPLETE_LAYER_3),
+ OSMO_VALUE_STRING(MSC_EV_FROM_RAN_UP_L2),
+ OSMO_VALUE_STRING(MSC_EV_FROM_RAN_CONN_RELEASED),
+
+ OSMO_VALUE_STRING(MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST),
+ OSMO_VALUE_STRING(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST),
+ OSMO_VALUE_STRING(MSC_T_EV_CN_CLOSE),
+ OSMO_VALUE_STRING(MSC_T_EV_MO_CLOSE),
+ OSMO_VALUE_STRING(MSC_T_EV_CLEAR_COMPLETE),
+ {}
+};
+
+static struct osmo_fsm msc_t_fsm = {
+ .name = "msc_t",
+ .states = msc_t_fsm_states,
+ .num_states = ARRAY_SIZE(msc_t_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msc_t_fsm_event_names,
+ .cleanup = msc_t_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msc_t_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&msc_t_fsm) == 0);
+}
+
+/* Send connection-oriented L3 message to RAN peer (MSC->[BSC|RNC]) */
+int msc_t_down_l2_co(struct msc_t *msc_t, const struct an_apdu *an_apdu, bool initial)
+{
+ int rc;
+ if (!msc_t->ran_conn) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Cannot Tx L2 message: no RAN conn\n");
+ return -EIO;
+ }
+
+ if (an_apdu->an_proto != msc_t->c.ran->an_proto) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Mismatching AN-APDU proto: %s -- Dropping message\n",
+ an_proto_name(an_apdu->an_proto));
+ return -EIO;
+ }
+
+ rc = ran_conn_down_l2_co(msc_t->ran_conn, an_apdu->msg, initial);
+ if (rc)
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to transfer message down to new RAN peer (rc=%d)\n", rc);
+ return rc;
+}
+
+struct gsm_network *msc_t_net(const struct msc_t *msc_t)
+{
+ return msub_net(msc_t->c.msub);
+}
+
+struct vlr_subscr *msc_t_vsub(const struct msc_t *msc_t)
+{
+ return msub_vsub(msc_t->c.msub);
+}
+
+struct msc_t *msc_t_alloc_without_ran_peer(struct msub *msub, struct ran_infra *ran)
+{
+ struct msc_t *msc_t;
+
+ msub_role_alloc(msub, MSC_ROLE_T, &msc_t_fsm, struct msc_t, ran);
+ msc_t = msub_msc_t(msub);
+ if (!msc_t)
+ return NULL;
+
+ return msc_t;
+}
+
+int msc_t_set_ran_peer(struct msc_t *msc_t, struct ran_peer *ran_peer)
+{
+ if (!ran_peer || !ran_peer->sri || !ran_peer->sri->ran) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Invalid RAN peer: %s\n", ran_peer ? ran_peer->fi->id : "NULL");
+ return -EINVAL;
+ }
+
+ if (ran_peer->sri->ran != msc_t->c.ran) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "This MSC-T was set up for %s, cannot assign RAN peer for %s\n",
+ osmo_rat_type_name(msc_t->c.ran->type), osmo_rat_type_name(ran_peer->sri->ran->type));
+ return -EINVAL;
+ }
+
+ /* Create a new ran_conn with a fresh conn_id for the outgoing initial message. The msc_t FSM definition ensures
+ * that the first message sent or received is a Connection-Oriented Initial message. */
+ msc_t->ran_conn = ran_conn_create_outgoing(ran_peer);
+ if (!msc_t->ran_conn) {
+ LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to create outgoing RAN conn\n");
+ return -EINVAL;
+ }
+ msc_t->ran_conn->msc_role = msc_t->c.fi;
+ msub_update_id(msc_t->c.msub);
+ return 0;
+}
+
+struct msc_t *msc_t_alloc(struct msub *msub, struct ran_peer *ran_peer)
+{
+ struct msc_t *msc_t = msc_t_alloc_without_ran_peer(msub, ran_peer->sri->ran);
+ if (!msc_t)
+ return NULL;
+ if (msc_t_set_ran_peer(msc_t, ran_peer)) {
+ msc_t_clear(msc_t);
+ return NULL;
+ }
+ return msc_t;
+}
+
+void msc_t_clear(struct msc_t *msc_t)
+{
+ if (!msc_t)
+ return;
+ osmo_fsm_inst_term(msc_t->c.fi, OSMO_FSM_TERM_REGULAR, msc_t->c.fi);
+}
diff --git a/src/libmsc/msc_t_remote.c b/src/libmsc/msc_t_remote.c
new file mode 100644
index 000000000..22c4e22fd
--- /dev/null
+++ b/src/libmsc/msc_t_remote.c
@@ -0,0 +1,226 @@
+/* The MSC-T role implementation variant that forwards requests to/from a remote MSC. */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/msc_t_remote.h>
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/e_link.h>
+
+static struct osmo_fsm msc_t_remote_fsm;
+
+static struct msc_t *msc_t_remote_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &msc_t_remote_fsm);
+ OSMO_ASSERT(fi->priv);
+ return fi->priv;
+}
+
+/* The idea is that this msc_t role is event-compatible to the "real" msc_t.c FSM, but instead of acting on the events
+ * directly, it forwards the events to a remote MSC-T role, via E-over-GSUP.
+ *
+ * [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_t_remote ----GSUP---> msc_a_remote --> msc_t ---BSSMAP--> [BSS]
+ * you are here^
+ */
+static int msc_t_remote_msg_down_to_remote_msc(struct msc_t *msc_t,
+ enum osmo_gsup_message_type message_type,
+ struct an_apdu *an_apdu)
+{
+ struct osmo_gsup_message m;
+ struct e_link *e = msc_t->c.remote_to;
+
+ if (!e) {
+ LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "No E link to remote MSC, cannot send AN-APDU\n");
+ return -1;
+ }
+
+ if (e_prep_gsup_msg(e, &m)) {
+ LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Error composing E-interface GSUP message\n");
+ return -1;
+ }
+ m.message_type = message_type;
+ if (an_apdu) {
+ if (gsup_msg_assign_an_apdu(&m, an_apdu)) {
+ LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Error composing E-interface GSUP message\n");
+ return -1;
+ }
+ }
+
+ return e_tx(e, &m);
+}
+
+/* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-- msc_t_remote <---GSUP---- msc_a_remote <-- msc_t <--BSSMAP--- [BSS]
+ * you are here^
+ */
+static int msc_t_remote_rx_gsup(struct msc_t *msc_t, const struct osmo_gsup_message *gsup_msg)
+{
+ uint32_t event;
+ struct an_apdu an_apdu;
+ int rc;
+
+ switch (gsup_msg->message_type) {
+ case OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST:
+ event = MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR:
+ case OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_RESULT:
+ event = MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE;
+ break;
+
+ case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST:
+ event = MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST;
+ break;
+
+ case OSMO_GSUP_MSGT_E_CLOSE:
+ case OSMO_GSUP_MSGT_E_ABORT:
+ case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
+ msc_t_clear(msc_t);
+ return 0;
+
+ default:
+ LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
+ osmo_gsup_message_type_name(gsup_msg->message_type));
+ return -1;
+ };
+
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-- msc_t_remote <---GSUP---- msc_a_remote <-- msc_t <--BSSMAP--- [BSS]
+ * ^you are here
+ */
+ gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
+ rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, event, &an_apdu);
+ if (an_apdu.msg)
+ msgb_free(an_apdu.msg);
+ return rc;
+}
+
+static void msc_t_remote_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_t *msc_t = msc_t_remote_priv(fi);
+ struct an_apdu *an_apdu;
+
+ switch (event) {
+
+ case MSC_REMOTE_EV_RX_GSUP:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-- msc_t_remote <---GSUP---- msc_a_remote <-- msc_t <--BSSMAP--- [BSS]
+ * you are here^
+ */
+ msc_t_remote_rx_gsup(msc_t, (const struct osmo_gsup_message*)data);
+ return;
+
+ case MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_t_remote ----GSUP---> going to create an msc_t if the request succeeds
+ * ^you are here
+ */
+ an_apdu = data;
+ msc_t_remote_msg_down_to_remote_msc(msc_t, OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST, an_apdu);
+ return;
+
+ case MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
+ /* [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a --> msc_t_remote ----GSUP---> msc_a_remote --> msc_t ---BSSMAP--> [BSS]
+ * ^you are here
+ */
+ an_apdu = data;
+ msc_t_remote_msg_down_to_remote_msc(msc_t, OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST, an_apdu);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_t_remote_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_t *msc_t = msc_t_remote_priv(fi);
+ if (msc_t->c.remote_to)
+ msc_t_remote_msg_down_to_remote_msc(msc_t, OSMO_GSUP_MSGT_E_CLOSE, NULL);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_t_remote_fsm_states[] = {
+ /* An FSM instance always starts in state 0. Define one just to be able to state_chg out of it. Root reason is
+ * that we're using MSC_T_ST_* enum values from msc_t.c, but don't need the first
+ * MSC_T_ST_PENDING_FIRST_CO_INITIAL_MSG. */
+ [0] = {
+ .name = "0",
+ .out_state_mask = 0
+ | S(MSC_T_ST_WAIT_HO_COMPLETE)
+ ,
+ },
+ [MSC_T_ST_WAIT_HO_COMPLETE] = {
+ .name = "WAIT_HO_COMPLETE",
+ .action = msc_t_remote_fsm_ready,
+ .in_event_mask = 0
+ | S(MSC_REMOTE_EV_RX_GSUP)
+ | S(MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST)
+ | S(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
+ ,
+ },
+};
+
+static struct osmo_fsm msc_t_remote_fsm = {
+ .name = "msc_t_remote",
+ .states = msc_t_remote_fsm_states,
+ .num_states = ARRAY_SIZE(msc_t_remote_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msc_t_fsm_event_names,
+ .cleanup = msc_t_remote_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msc_t_remote_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&msc_t_remote_fsm) == 0);
+}
+
+struct msc_t *msc_t_remote_alloc(struct msub *msub, struct ran_infra *ran,
+ const uint8_t *remote_msc_name, size_t remote_msc_name_len)
+{
+ struct msc_t *msc_t;
+
+ msub_role_alloc(msub, MSC_ROLE_T, &msc_t_remote_fsm, struct msc_t, ran);
+ msc_t = msub_msc_t(msub);
+ if (!msc_t)
+ return NULL;
+
+ msc_t->c.remote_to = e_link_alloc(msub_net(msub)->gcm, msc_t->c.fi, remote_msc_name, remote_msc_name_len);
+ if (!msc_t->c.remote_to) {
+ LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Failed to set up E link over GSUP to remote MSC\n");
+ msc_t_clear(msc_t);
+ return NULL;
+ }
+
+ osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_COMPLETE, 0, 0);
+ return msc_t;
+}
diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c
index 9bc8f3aae..bb3639257 100644
--- a/src/libmsc/msc_vty.c
+++ b/src/libmsc/msc_vty.c
@@ -28,6 +28,8 @@
#include <inttypes.h>
#include <limits.h>
+#include <osmocom/core/use_count.h>
+
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/gsm/protocol/gsm_04_14.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
@@ -46,10 +48,11 @@
#include <osmocom/msc/vty.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsm_subscriber.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/transaction.h>
#include <osmocom/msc/db.h>
-#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/sms_queue.h>
#include <osmocom/msc/silent_call.h>
#include <osmocom/msc/gsm_04_80.h>
@@ -59,6 +62,8 @@
#include <osmocom/msc/rrlp.h>
#include <osmocom/msc/vlr_sgs.h>
#include <osmocom/msc/sgs_vty.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/ran_peer.h>
static struct gsm_network *gsmnet = NULL;
@@ -504,6 +509,51 @@ DEFUN(cfg_msc_no_sms_over_gsup, cfg_msc_no_sms_over_gsup_cmd,
return CMD_SUCCESS;
}
+/* FIXME: This should rather be in the form of
+ * handover-number range 001234xxx
+ * and
+ * handover-number range 001234xxx FIRST LAST
+ */
+DEFUN(cfg_msc_handover_number_range, cfg_msc_handover_number_range_cmd,
+ "handover-number range MSISDN_FIRST MSISDN_LAST",
+ "Configure a range of MSISDN to be assigned to incoming inter-MSC Handovers for call forwarding.\n"
+ "Configure a handover number range\n"
+ "First Handover Number MSISDN\n"
+ "Last Handover Number MSISDN\n")
+{
+ char *endp;
+ uint64_t range_start;
+ uint64_t range_end;
+
+ /* FIXME leading zeros?? */
+
+ errno = 0;
+ range_start = strtoull(argv[0], &endp, 10);
+ if (errno || *endp != '\0') {
+ vty_out(vty, "%% Error parsing handover-number range start: %s%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ errno = 0;
+ range_end = strtoull(argv[1], &endp, 10);
+ if (errno || *endp != '\0') {
+ vty_out(vty, "%% Error parsing handover-number range end: %s%s",
+ argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (range_start > range_end) {
+ vty_out(vty, "%% Error: handover-number range end must be > than the range start, but"
+ " %"PRIu64" > %"PRIu64"%s", range_start, range_end, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gsmnet->handover_number.range_start = range_start;
+ gsmnet->handover_number.range_end = range_end;
+ return CMD_SUCCESS;
+}
+
static int config_write_msc(struct vty *vty)
{
vty_out(vty, "msc%s", VTY_NEWLINE);
@@ -546,6 +596,11 @@ static int config_write_msc(struct vty *vty)
if (gsmnet->sms_over_gsup)
vty_out(vty, " sms-over-gsup%s", VTY_NEWLINE);
+ if (gsmnet->handover_number.range_start || gsmnet->handover_number.range_end)
+ vty_out(vty, " handover-number range %"PRIu64" %"PRIu64"%s",
+ gsmnet->handover_number.range_start, gsmnet->handover_number.range_end,
+ VTY_NEWLINE);
+
mgcp_client_config_write(vty, " ");
#ifdef BUILD_IU
ranap_iu_vty_config_write(vty, " ");
@@ -557,81 +612,81 @@ static int config_write_msc(struct vty *vty)
DEFUN(show_bsc, show_bsc_cmd,
"show bsc", SHOW_STR "BSC\n")
{
- struct bsc_context *bsc_ctx;
- struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(gsmnet->a.cs7_instance);
-
- llist_for_each_entry(bsc_ctx, &gsmnet->a.bscs, list) {
- vty_out(vty, "BSC %s%s", osmo_sccp_addr_name(ss7, &bsc_ctx->bsc_addr), VTY_NEWLINE);
+ struct ran_peer *rp;
+ llist_for_each_entry(rp, &gsmnet->a.sri->ran_peers, entry) {
+ vty_out(vty, "BSC %s %s%s",
+ osmo_sccp_inst_addr_name(gsmnet->a.sri->sccp, &rp->peer_addr),
+ osmo_fsm_inst_state_name(rp->fi),
+ VTY_NEWLINE);
}
return CMD_SUCCESS;
}
-static void vty_conn_hdr(struct vty *vty)
+/*
+_Subscriber_______________________________________ _LAC_ _RAN___________________ _MSC-A_state_________ _MSC-A_use_
+IMSI-123456789012345:MSISDN-12345:TMSI-0x12345678 1 GERAN-A-4294967295:A5-3 WAIT_CLASSMARK_UPDATE 2=cm_service,trans_cc
+IMSI-123456789012356:MSISDN-234567:TMSI-0x123ABC78 65535 UTRAN-Iu-4294967295 COMMUNICATING 2=cm_service,trans_sms
+IMSI-123456789012367:MSISDN-98712345890:TMSI-0xF.. - EUTRAN-SGs RELEASING 0=none
+IMSI-123456789012378:HONR-12345432101 2 MSC-901-700-423:9876 REMOTE_MSC_A 1=inter_msc
+*/
+static void vty_dump_one_conn(struct vty *vty, const struct msub *msub, int *idx)
{
- unsigned lnum = 0;
- struct ran_conn *conn;
-
- llist_for_each_entry(conn, &gsmnet->ran_conns, entry)
- lnum++;
+ struct msc_a *msc_a = msub_msc_a(msub);
+ struct vlr_subscr *vsub = msub_vsub(msub);
+ char buf[128];
- if (lnum)
- vty_out(vty, "--ConnId RAN --LAC Use --Tokens C A5 State ------------ Subscriber%s",
+ if (!(*idx))
+ vty_out(vty,
+ "_Subscriber_______________________________________ _LAC_ _RAN___________________"
+ " _MSC-A_state_________ _MSC-A_use_%s",
VTY_NEWLINE);
-}
-
-static void vty_dump_one_conn(struct vty *vty, const struct ran_conn *conn)
-{
- vty_out(vty, "%08x %3s %5u %3u %08x %c /%1u %27s %22s%s",
- conn->a.conn_id,
- osmo_rat_type_name(conn->via_ran),
- conn->lac,
- conn->use_count,
- conn->use_tokens,
- conn->received_cm_service_request ? 'C' : '-',
- conn->geran_encr.alg_id,
- conn->fi ? osmo_fsm_inst_state_name(conn->fi) : "-",
- conn->vsub ? vlr_subscr_name(conn->vsub) : "-",
+ (*idx)++;
+
+ vty_out(vty, "%50s %5u %23s %20s %d=%s%s",
+ vlr_subscr_short_name(msub_vsub(msub), 50),
+ vsub ? vsub->cgi.lai.lac : 0,
+ msub_ran_conn_name(msub),
+ osmo_fsm_inst_state_name(msc_a->c.fi),
+ osmo_use_count_total(&msc_a->use_count),
+ osmo_use_count_name_buf(buf, sizeof(buf), &msc_a->use_count),
VTY_NEWLINE);
}
DEFUN(show_msc_conn, show_msc_conn_cmd,
"show connection", SHOW_STR "Subscriber Connections\n")
{
- struct ran_conn *conn;
-
- vty_conn_hdr(vty);
- llist_for_each_entry(conn, &gsmnet->ran_conns, entry)
- vty_dump_one_conn(vty, conn);
-
+ struct msub *msub;
+ int idx = 0;
+ llist_for_each_entry(msub, &msub_list, entry) {
+ vty_dump_one_conn(vty, msub, &idx);
+ }
return CMD_SUCCESS;
}
static void vty_trans_hdr(struct vty *vty)
{
- unsigned lnum = 0;
- struct gsm_trans *trans;
-
- llist_for_each_entry(trans, &gsmnet->trans_list, entry)
- lnum++;
+ if (llist_empty(&gsmnet->trans_list))
+ return;
- if (lnum)
- vty_out(vty, "--ConnId -P TI -CallRef [--- Proto ---] ------------ Subscriber%s",
- VTY_NEWLINE);
+ vty_out(vty,
+ "_Subscriber_______________________________________ _RAN___________________"
+ " _P__ TI CallRef_ _state_%s",
+ VTY_NEWLINE);
}
static const char *get_trans_proto_str(const struct gsm_trans *trans)
{
static char buf[256];
- switch (trans->protocol) {
- case GSM48_PDISC_CC:
+ switch (trans->type) {
+ case TRANS_CC:
snprintf(buf, sizeof(buf), "%s %4u %4u",
gsm48_cc_state_name(trans->cc.state),
trans->cc.Tcurrent,
trans->cc.T308_second);
break;
- case GSM48_PDISC_SMS:
+ case TRANS_SMS:
snprintf(buf, sizeof(buf), "%s %s",
gsm411_cp_state_name(trans->sms.smc_inst.cp_state),
gsm411_rp_state_name(trans->sms.smr_inst.rp_state));
@@ -646,13 +701,13 @@ static const char *get_trans_proto_str(const struct gsm_trans *trans)
static void vty_dump_one_trans(struct vty *vty, const struct gsm_trans *trans)
{
- vty_out(vty, "%08x %s %02u %08x [%s] %22s%s",
- trans->conn ? trans->conn->a.conn_id : 0,
- gsm48_pdisc_name(trans->protocol),
+ vty_out(vty, "%50s %23s %4s %02u %08x %s%s",
+ vlr_subscr_short_name(msc_a_vsub(trans->msc_a), 50),
+ msub_ran_conn_name(trans->msc_a->c.msub),
+ trans_type_name(trans->type),
trans->transaction_id,
trans->callref,
get_trans_proto_str(trans),
- trans->vsub ? vlr_subscr_name(trans->vsub) : "-",
VTY_NEWLINE);
}
@@ -715,18 +770,6 @@ static void subscr_dump_full_vty(struct vty *vty, struct vlr_subscr *vsub)
vty_out(vty, " LA allowed: %s%s",
vsub->la_allowed ? "true" : "false", VTY_NEWLINE);
-#if 0
- /* TODO: add this to vlr_subscr? */
- if (vsub->auth_info.auth_algo != AUTH_ALGO_NONE) {
- struct gsm_auth_info *i = &vsub->auth_info;
- vty_out(vty, " A3A8 algorithm id: %d%s",
- i->auth_algo, VTY_NEWLINE);
- vty_out(vty, " A3A8 Ki: %s%s",
- osmo_hexdump(i->a3a8_ki, i->a3a8_ki_len),
- VTY_NEWLINE);
- }
-#endif
-
if (vsub->last_tuple) {
struct vlr_auth_tuple *t = vsub->last_tuple;
vty_out(vty, " A3A8 last tuple (used %d times):%s",
@@ -762,9 +805,11 @@ static void subscr_dump_full_vty(struct vty *vty, struct vlr_subscr *vsub)
/* Connection */
if (vsub->msc_conn_ref) {
- struct ran_conn *conn = vsub->msc_conn_ref;
- vty_conn_hdr(vty);
- vty_dump_one_conn(vty, conn);
+ struct msub *msub = vsub->msc_conn_ref;
+ int idx = 0;
+ if (msub) {
+ vty_dump_one_conn(vty, msub, &idx);
+ }
}
/* Transactions */
@@ -1214,7 +1259,7 @@ DEFUN(subscriber_ussd_notify,
"Text of USSD message to send\n")
{
char *text;
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
int level;
@@ -1231,19 +1276,19 @@ DEFUN(subscriber_ussd_notify,
return CMD_WARNING;
}
- conn = connection_for_subscr(vsub);
- if (!conn) {
- vty_out(vty, "%% An active connection is required for %s %s%s",
+ msc_a = msc_a_for_vsub(vsub, true);
+ if (!msc_a || msc_a->c.remote_to) {
+ vty_out(vty, "%% An active connection and local MSC-A role is required for %s %s%s",
argv[0], argv[1], VTY_NEWLINE);
vlr_subscr_put(vsub, VSUB_USE_VTY);
talloc_free(text);
return CMD_WARNING;
}
- msc_send_ussd_notify(conn, level, text);
+ msc_send_ussd_notify(msc_a, level, text);
/* FIXME: since we don't allocate a transaction here,
* we use dummy GSM 04.07 transaction ID. */
- msc_send_ussd_release_complete(conn, 0x00);
+ msc_send_ussd_release_complete(msc_a, 0x00);
vlr_subscr_put(vsub, VSUB_USE_VTY);
talloc_free(text);
@@ -1256,7 +1301,7 @@ DEFUN(subscriber_paging,
SUBSCR_HELP "Issue an empty Paging for the subscriber (for debugging)\n")
{
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
- struct subscr_request *req;
+ struct paging_request *req;
if (!vsub) {
vty_out(vty, "%% No subscriber found for %s %s%s",
@@ -1264,7 +1309,8 @@ DEFUN(subscriber_paging,
return CMD_WARNING;
}
- req = subscr_request_conn(vsub, NULL, NULL, "manual Paging from VTY", SGSAP_SERV_IND_CS_CALL);
+ req = paging_request_start(vsub, PAGING_CAUSE_CALL_CONVERSATIONAL,
+ NULL, NULL, "manual Paging from VTY");
if (req)
vty_out(vty, "%% paging subscriber%s", VTY_NEWLINE);
else
@@ -1308,7 +1354,7 @@ DEFUN(subscriber_mstest_close,
"Loop Type F\n"
"Loop Type I\n")
{
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
const char *loop_str;
int loop_mode;
@@ -1322,15 +1368,15 @@ DEFUN(subscriber_mstest_close,
loop_str = argv[2];
loop_mode = loop_by_char(loop_str[0]);
- conn = connection_for_subscr(vsub);
- if (!conn) {
+ msc_a = msc_a_for_vsub(vsub, true);
+ if (!msc_a) {
vty_out(vty, "%% An active connection is required for %s %s%s",
argv[0], argv[1], VTY_NEWLINE);
vlr_subscr_put(vsub, VSUB_USE_VTY);
return CMD_WARNING;
}
- gsm0414_tx_close_tch_loop_cmd(conn, loop_mode);
+ gsm0414_tx_close_tch_loop_cmd(msc_a, loop_mode);
return CMD_SUCCESS;
}
@@ -1341,7 +1387,7 @@ DEFUN(subscriber_mstest_open,
SUBSCR_HELP "Send a TS 04.14 MS Test Command to subscriber\n"
"Open a TCH Loop inside the MS\n")
{
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
if (!vsub) {
@@ -1350,15 +1396,15 @@ DEFUN(subscriber_mstest_open,
return CMD_WARNING;
}
- conn = connection_for_subscr(vsub);
- if (!conn) {
+ msc_a = msc_a_for_vsub(vsub, true);
+ if (!msc_a) {
vty_out(vty, "%% An active connection is required for %s %s%s",
argv[0], argv[1], VTY_NEWLINE);
vlr_subscr_put(vsub, VSUB_USE_VTY);
return CMD_WARNING;
}
- gsm0414_tx_open_loop_cmd(conn);
+ gsm0414_tx_open_loop_cmd(msc_a);
return CMD_SUCCESS;
}
@@ -1394,14 +1440,20 @@ static int scall_cbfn(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct scall_signal_data *sigdata = signal_data;
- struct vty *vty = sigdata->data;
+ struct vty *vty = sigdata->vty;
+
+ if (!vty_is_active(vty))
+ return 0;
switch (signal) {
case S_SCALL_SUCCESS:
- vty_out(vty, "%% silent call success%s", VTY_NEWLINE);
+ vty_out(vty, "%% Silent call success%s", VTY_NEWLINE);
+ break;
+ case S_SCALL_FAILED:
+ vty_out(vty, "%% Silent call failed%s", VTY_NEWLINE);
break;
- case S_SCALL_EXPIRED:
- vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE);
+ case S_SCALL_DETACHED:
+ vty_out(vty, "%% Silent call ended%s", VTY_NEWLINE);
break;
}
return 0;
@@ -1692,12 +1744,16 @@ void msc_vty_init(struct gsm_network *msc_network)
install_element(MSC_NODE, &cfg_msc_emergency_msisdn_cmd);
install_element(MSC_NODE, &cfg_msc_sms_over_gsup_cmd);
install_element(MSC_NODE, &cfg_msc_no_sms_over_gsup_cmd);
+ install_element(MSC_NODE, &cfg_msc_handover_number_range_cmd);
+
+ neighbor_ident_vty_init(msc_network);
mgcp_client_vty_init(msc_network, MSC_NODE, &msc_network->mgw.conf);
#ifdef BUILD_IU
- ranap_iu_vty_init(MSC_NODE, &msc_network->iu.rab_assign_addr_enc);
+ ranap_iu_vty_init(MSC_NODE, (enum ranap_nsap_addr_enc*)&msc_network->iu.rab_assign_addr_enc);
#endif
sgs_vty_init();
+
osmo_fsm_vty_add_cmds();
osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL);
diff --git a/src/libmsc/msub.c b/src/libmsc/msub.c
new file mode 100644
index 000000000..490073289
--- /dev/null
+++ b/src/libmsc/msub.c
@@ -0,0 +1,590 @@
+/* Manage all MSC roles of a connected subscriber (MSC-A, MSC-I, MSC-T) */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/gsm/gsm48.h>
+
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/e_link.h>
+
+const struct value_string msc_role_names[] = {
+ { MSC_ROLE_A, "MSC-A" },
+ { MSC_ROLE_I, "MSC-I" },
+ { MSC_ROLE_T, "MSC-T" },
+ {}
+};
+
+LLIST_HEAD(msub_list);
+
+#define for_each_msub_role(msub, role_idx) \
+ for ((role_idx) = 0; (role_idx) < ARRAY_SIZE((msub)->role); (role_idx)++) \
+ if ((msub)->role[role_idx])
+
+enum msub_fsm_state {
+ MSUB_ST_ACTIVE,
+ MSUB_ST_TERMINATING,
+};
+
+enum msub_fsm_event {
+ MSUB_EV_ROLE_TERMINATED,
+};
+
+static void msub_check_for_release(struct osmo_fsm_inst *fi)
+{
+ struct msub *msub = fi->priv;
+ struct msc_role_common *msc_role_a_c;
+ enum msc_role role_idx;
+ int role_present[MSC_ROLES_COUNT] = {};
+ struct osmo_fsm_inst *child;
+
+ /* See what child FSMs are still present. A caller might exchange roles by first allocating a new one as child
+ * of this FSM, and then exchanging the msub->role[] pointer. Even though the currently active role is removing
+ * itself from msub, we can still see whether another one is pending as a child of this msub. */
+ llist_for_each_entry(child, &fi->proc.children, proc.child) {
+ struct msc_role_common *c = child->priv;
+ role_present[c->role]++;
+ if (c->role == MSC_ROLE_A)
+ msc_role_a_c = c;
+ }
+
+ /* Log. */
+ for (role_idx = 0; role_idx < ARRAY_SIZE(role_present); role_idx++) {
+ if (!role_present[role_idx])
+ continue;
+ LOG_MSUB(msub, LOGL_DEBUG, "%d %s still active\n", role_present[role_idx], msc_role_name(role_idx));
+ }
+
+ /* To remain valid, there must be both an MSC-A role and one of MSC-I or MSC-T;
+ * except, SGs connections need no MSC-I or MSC-T. */
+ if (role_present[MSC_ROLE_A]
+ && (role_present[MSC_ROLE_I] || role_present[MSC_ROLE_T]
+ || (msc_role_a_c && msc_role_a_c->ran->type == OSMO_RAT_EUTRAN_SGS)))
+ return;
+
+ /* The subscriber has become invalid. Go to terminating state to clearly signal that this msub is definitely
+ * going now. */
+ osmo_fsm_inst_state_chg(fi, MSUB_ST_TERMINATING, 0, 0);
+}
+
+void msub_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msub *msub = fi->priv;
+ struct osmo_fsm_inst *role_fi;
+
+ switch (event) {
+ case MSUB_EV_ROLE_TERMINATED:
+ role_fi = data;
+ /* Role implementations are required to pass their own osmo_fsm_inst pointer to osmo_fsm_inst_term(). */
+ msub_remove_role(msub, role_fi);
+ msub_check_for_release(fi);
+ return;
+ default:
+ return;
+ }
+}
+
+void msub_fsm_terminating_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+void msub_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msub *msub = fi->priv;
+ LOG_MSUB(msub, LOGL_DEBUG, "Free\n");
+ msub_set_vsub(msub, NULL);
+ llist_del(&msub->entry);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msub_fsm_states[] = {
+ [MSUB_ST_ACTIVE] = {
+ .name = "active",
+ .in_event_mask = S(MSUB_EV_ROLE_TERMINATED),
+ .out_state_mask = S(MSUB_ST_TERMINATING),
+ .action = msub_fsm_active,
+ },
+ [MSUB_ST_TERMINATING] = {
+ .name = "terminating",
+ .onenter = msub_fsm_terminating_onenter,
+ },
+};
+
+static const struct value_string msub_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MSUB_EV_ROLE_TERMINATED),
+ {}
+};
+
+struct osmo_fsm msub_fsm = {
+ .name = "msub_fsm",
+ .states = msub_fsm_states,
+ .num_states = ARRAY_SIZE(msub_fsm_states),
+ .log_subsys = DMSC,
+ .event_names = msub_fsm_event_names,
+ .cleanup = msub_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void msub_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&msub_fsm) == 0);
+}
+
+struct msc_role_common *_msub_role_alloc(struct msub *msub, enum msc_role role, struct osmo_fsm *role_fsm,
+ size_t struct_size, const char *struct_name, struct ran_infra *ran)
+{
+ struct osmo_fsm_inst *fi;
+ struct msc_role_common *c;
+
+ fi = osmo_fsm_inst_alloc_child(role_fsm, msub->fi, MSUB_EV_ROLE_TERMINATED);
+ OSMO_ASSERT(fi);
+
+ c = (struct msc_role_common*)talloc_named_const(fi, struct_size, struct_name);
+ OSMO_ASSERT(c);
+ memset(c, 0, struct_size);
+ fi->priv = c;
+
+ *c = (struct msc_role_common){
+ .role = role,
+ .fi = fi,
+ .ran = ran,
+ };
+
+ msub_set_role(msub, fi);
+ return c;
+}
+
+struct msub *msub_alloc(struct gsm_network *net)
+{
+ struct msub *msub;
+ struct osmo_fsm_inst *msub_fi = osmo_fsm_inst_alloc(&msub_fsm, net, NULL, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(msub_fi);
+
+ msub = talloc(msub_fi, struct msub);
+ OSMO_ASSERT(msub);
+ msub_fi->priv = msub;
+ *msub = (struct msub){
+ .net = net,
+ .fi = msub_fi,
+ };
+
+ llist_add_tail(&msub->entry, &msub_list);
+ return msub;
+}
+
+/* Careful: the subscriber may not yet be authenticated, or may already be in release. Better use
+ * msc_a_for_vsub(for_vsub, true) to make sure you don't use an invalid conn. */
+struct msub *msub_for_vsub(const struct vlr_subscr *for_vsub)
+{
+ struct msub *msub;
+ if (!for_vsub)
+ return NULL;
+
+ llist_for_each_entry(msub, &msub_list, entry) {
+ if (msub->vsub == for_vsub)
+ return msub;
+ }
+
+ return NULL;
+}
+
+const char *msub_name(const struct msub *msub)
+{
+ return vlr_subscr_name(msub? msub->vsub : NULL);
+}
+
+void msub_set_role(struct msub *msub, struct osmo_fsm_inst *msc_role)
+{
+ struct osmo_fsm_inst *prev_role;
+ struct msc_role_common *c;
+
+ OSMO_ASSERT(msc_role);
+ c = msc_role->priv;
+
+ prev_role = msub->role[c->role];
+ if (prev_role)
+ LOGPFSML(prev_role, LOGL_DEBUG, "Replaced by another %s\n", msc_role_name(c->role));
+
+ c->msub = msub;
+ msub->role[c->role] = msc_role;
+ msub_update_id(msub);
+
+ if (prev_role) {
+ struct msc_role_common *prev_c = prev_role->priv;
+ switch (prev_c->role) {
+ case MSC_ROLE_I:
+ msc_i_clear(prev_role->priv);
+ break;
+ case MSC_ROLE_T:
+ msc_t_clear(prev_role->priv);
+ break;
+ default:
+ osmo_fsm_inst_term(prev_role, OSMO_FSM_TERM_REQUEST, prev_role);
+ break;
+ }
+ }
+}
+
+void msub_remove_role(struct msub *msub, struct osmo_fsm_inst *fi)
+{
+ enum msc_role idx;
+ struct msc_role_common *c;
+ if (!msub || !fi)
+ return;
+
+ c = fi->priv;
+ LOG_MSUB(msub, LOGL_DEBUG, "%s terminated\n", msc_role_name(c->role));
+
+ for_each_msub_role(msub, idx) {
+ if (msub->role[idx] == fi)
+ msub->role[idx] = NULL;
+ }
+}
+
+struct msc_a *msub_msc_a(const struct msub *msub)
+{
+ struct osmo_fsm_inst *fi;
+ if (!msub)
+ return NULL;
+ fi = msub->role[MSC_ROLE_A];
+ if (!fi)
+ return NULL;
+ return (struct msc_a*)fi->priv;
+}
+
+struct msc_i *msub_msc_i(const struct msub *msub)
+{
+ struct osmo_fsm_inst *fi;
+ if (!msub)
+ return NULL;
+ fi = msub->role[MSC_ROLE_I];
+ if (!fi)
+ return NULL;
+ return (struct msc_i*)fi->priv;
+}
+
+struct msc_t *msub_msc_t(const struct msub *msub)
+{
+ struct osmo_fsm_inst *fi;
+ if (!msub)
+ return NULL;
+ fi = msub->role[MSC_ROLE_T];
+ if (!fi)
+ return NULL;
+ return (struct msc_t*)fi->priv;
+}
+
+/* Return the ran_conn of the MSC-I role, if available. If the MSC-I role is handled by a remote MSC, return NULL. */
+struct ran_conn *msub_ran_conn(const struct msub *msub)
+{
+ struct msc_i *msc_i = msub_msc_i(msub);
+ if (!msc_i)
+ return NULL;
+ return msc_i->ran_conn;
+}
+
+static struct ran_infra *msub_ran(const struct msub *msub)
+{
+ int i;
+ struct msc_role_common *c;
+
+ for (i = 0; i < MSC_ROLES_COUNT; i++) {
+ if (!msub->role[i])
+ continue;
+ c = msub->role[i]->priv;
+ if (!c->ran)
+ continue;
+ return c->ran;
+ }
+
+ return &msc_ran_infra[OSMO_RAT_UNKNOWN];
+}
+
+const char *msub_ran_conn_name(const struct msub *msub)
+{
+ struct msc_i *msc_i = msub_msc_i(msub);
+ struct msc_t *msc_t = msub_msc_t(msub);
+ if (msc_i && msc_i->c.remote_to)
+ return e_link_name(msc_i->c.remote_to);
+ if (msc_i && msc_i->ran_conn)
+ return ran_conn_name(msc_i->ran_conn);
+ if (msc_t && msc_t->c.remote_to)
+ return e_link_name(msc_t->c.remote_to);
+ if (msc_t && msc_t->ran_conn)
+ return ran_conn_name(msc_t->ran_conn);
+ return osmo_rat_type_name(msub_ran(msub)->type);
+}
+
+int msub_set_vsub(struct msub *msub, struct vlr_subscr *vsub)
+{
+ OSMO_ASSERT(msub);
+ if (msub->vsub == vsub)
+ return 0;
+ if (msub->vsub && vsub) {
+ LOG_MSUB(msub, LOGL_ERROR,
+ "Changing a connection's VLR Subscriber is not allowed: not changing to %s\n",
+ vlr_subscr_name(vsub));
+ return -ENOTSUP;
+ }
+ if (vsub) {
+ struct msub *other_msub = msub_for_vsub(vsub);
+ if (other_msub) {
+ struct msc_a *msc_a = msub_msc_a(msub);
+ struct msc_a *other_msc_a = msub_msc_a(other_msub);
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Cannot associate with VLR subscr, another connection is already active%s%s\n",
+ other_msc_a ? " at " : "", other_msc_a ? other_msc_a->c.fi->id : "");
+ LOG_MSC_A(other_msc_a, LOGL_ERROR, "Attempt to associate a second subscriber connection%s%s\n",
+ msc_a ? " at " : "", msc_a ? msc_a->c.fi->id : "");
+ if (other_msc_a && msc_a_in_release(other_msc_a)) {
+ LOG_MSC_A(other_msc_a, LOGL_ERROR,
+ "Another connection for this subscriber is coming up, since this"
+ " is already in release, forcefully discarding it\n");
+ osmo_fsm_inst_term(other_msc_a->c.fi, OSMO_FSM_TERM_ERROR, other_msc_a->c.fi);
+ /* Count this as "recovered from duplicate connection" error and do associate. */
+ } else
+ return -EINVAL;
+ }
+ }
+ if (msub->vsub) {
+ vlr_subscr_put(msub->vsub, VSUB_USE_MSUB);
+ msub->vsub = NULL;
+ }
+ if (vsub) {
+ vlr_subscr_get(vsub, VSUB_USE_MSUB);
+ msub->vsub = vsub;
+ vsub->cs.attached_via_ran = msub_ran(msub)->type;
+ msub_update_id(msub);
+ }
+ return 0;
+}
+
+struct vlr_subscr *msub_vsub(const struct msub *msub)
+{
+ return msub ? msub->vsub : NULL;
+}
+
+struct gsm_network *msub_net(const struct msub *msub)
+{
+ OSMO_ASSERT(msub->net);
+ return msub->net;
+}
+
+int msub_role_to_role_event(struct msub *msub, enum msc_role from_role, enum msc_role to_role)
+{
+ switch (from_role) {
+ case MSC_ROLE_A:
+ switch (to_role) {
+ case MSC_ROLE_I:
+ return MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
+ case MSC_ROLE_T:
+ return MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
+ default:
+ break;
+ }
+ break;
+
+ case MSC_ROLE_I:
+ switch (to_role) {
+ case MSC_ROLE_A:
+ return MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST;
+ default:
+ break;
+ }
+ break;
+
+ case MSC_ROLE_T:
+ switch (to_role) {
+ case MSC_ROLE_A:
+ return MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ LOG_MSUB(msub, LOGL_ERROR, "Cannot tx DTAP from %s to %s\n", msc_role_name(from_role), msc_role_name(to_role));
+ return -1;
+}
+
+/* The caller retains ownership of the an_apdu_msg -- don't forget to msgb_free() it. */
+int _msub_role_dispatch(struct msub *msub, enum msc_role to_role, uint32_t to_role_event, const struct an_apdu *an_apdu,
+ const char *file, int line)
+{
+ struct osmo_fsm_inst *to_fi = msub->role[to_role];
+
+ if (!to_fi) {
+ LOG_MSUB_CAT_SRC(msub, DMSC, LOGL_ERROR, file, line,
+ "Cannot tx event to %s, no such role defined\n", msc_role_name(to_role));
+ return -EINVAL;
+ }
+
+ return _osmo_fsm_inst_dispatch(to_fi, to_role_event, (void*)an_apdu, file, line);
+}
+
+/* The caller retains ownership of the an_apdu_msg -- don't forget to msgb_free() it. */
+int msub_tx_an_apdu(struct msub *msub, enum msc_role from_role, enum msc_role to_role, struct an_apdu *an_apdu)
+{
+ int event = msub_role_to_role_event(msub, from_role, to_role);
+ if (event < 0)
+ return event;
+ return msub_role_dispatch(msub, to_role, event, an_apdu);
+}
+
+static void _msub_update_id(struct msub *msub, const char *subscr_name)
+{
+ enum msc_role idx;
+ struct msc_a *msc_a = msub_msc_a(msub);
+ struct vlr_subscr *vsub = msub_vsub(msub);
+ const char *compl_l3_name = NULL;
+ char id[128];
+
+ if (msc_a)
+ compl_l3_name = get_value_string_or_null(complete_layer3_type_names, msc_a->complete_layer3_type);
+ if (!compl_l3_name)
+ compl_l3_name = "no-compl-l3";
+
+ snprintf(id, sizeof(id), "%s:%s:%s", subscr_name, msub_ran_conn_name(msub), compl_l3_name);
+ osmo_identifier_sanitize_buf(id, NULL, '-');
+
+ for_each_msub_role(msub, idx) {
+ osmo_fsm_inst_update_id(msub->role[idx], id);
+ }
+ if (vsub) {
+ if (vsub->lu_fsm)
+ osmo_fsm_inst_update_id(vsub->lu_fsm, id);
+ if (vsub->auth_fsm)
+ osmo_fsm_inst_update_id(vsub->auth_fsm, id);
+ if (vsub->proc_arq_fsm)
+ osmo_fsm_inst_update_id(vsub->proc_arq_fsm, id);
+ }
+}
+
+/* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */
+void msub_update_id_from_mi(struct msub *msub, const uint8_t mi[], uint8_t mi_len)
+{
+ _msub_update_id(msub, osmo_mi_name(mi, mi_len));
+}
+
+/* Update msub->fi id string from current msub->vsub and msub->complete_layer3_type. */
+void msub_update_id(struct msub *msub)
+{
+ if (!msub)
+ return;
+ _msub_update_id(msub, vlr_subscr_name(msub->vsub));
+}
+
+/* Iterate all msub instances that are relevant for this subscriber, and update FSM ID strings for all of the FSM
+ * instances. */
+void msub_update_id_for_vsub(struct vlr_subscr *for_vsub)
+{
+ struct msub *msub;
+ if (!for_vsub)
+ return;
+
+ llist_for_each_entry(msub, &msub_list, entry) {
+ if (msub->vsub == for_vsub)
+ msub_update_id(msub);
+ }
+}
+
+void msc_role_forget_conn(struct osmo_fsm_inst *role, struct ran_conn *conn)
+{
+ struct msc_i *old_i = role->priv;
+ struct msc_t *old_t = role->priv;
+ struct msc_role_common *c = role->priv;
+ struct ran_conn **conn_p = NULL;
+
+ switch (c->role) {
+ case MSC_ROLE_I:
+ conn_p = &old_i->ran_conn;
+ break;
+
+ case MSC_ROLE_T:
+ conn_p = &old_t->ran_conn;
+ break;
+ default:
+ break;
+ }
+
+ if (!conn_p)
+ return;
+
+ if (*conn_p != conn)
+ return;
+
+ (*conn_p)->msc_role = NULL;
+ *conn_p = NULL;
+}
+
+struct msgb *msc_role_ran_encode(struct osmo_fsm_inst *fi, const struct ran_msg *ran_msg)
+{
+ struct msc_role_common *c = fi->priv;
+ struct msgb *msg;
+ if (!c->ran->ran_encode) {
+ LOGPFSML(fi, LOGL_ERROR, "Cannot encode %s: no NAS encoding function defined for RAN type %s\n",
+ ran_msg_type_name(ran_msg->msg_type), osmo_rat_type_name(c->ran->type));
+ return NULL;
+ }
+ msg = c->ran->ran_encode(fi, ran_msg);
+ if (!msg)
+ LOGPFSML(fi, LOGL_ERROR, "Failed to encode %s\n", ran_msg_type_name(ran_msg->msg_type));
+ return msg;
+}
+
+int msc_role_ran_decode(struct osmo_fsm_inst *fi, const struct an_apdu *an_apdu,
+ ran_decode_cb_t decode_cb, void *decode_cb_data)
+{
+ struct ran_dec ran_dec;
+ struct msc_role_common *c = fi->priv;
+ if (!an_apdu) {
+ LOGPFSML(fi, LOGL_ERROR, "NULL AN-APDU\n");
+ return -EINVAL;
+ }
+ if (an_apdu->an_proto != c->ran->an_proto) {
+ LOGPFSML(fi, LOGL_ERROR, "Unexpected AN-APDU protocol: %s\n", an_proto_name(an_apdu->an_proto));
+ return -EINVAL;
+ }
+ if (!an_apdu->msg) {
+ LOGPFSML(fi, LOGL_DEBUG, "No PDU in this AN-APDU\n");
+ return 0;
+ }
+ ran_dec = (struct ran_dec) {
+ .caller_fi = fi,
+ .caller_data = decode_cb_data,
+ .decode_cb = decode_cb,
+ };
+ if (!c->ran->ran_dec_l2) {
+ LOGPFSML(fi, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n",
+ osmo_rat_type_name(c->ran->type));
+ return -ENOTSUP;
+ }
+ return c->ran->ran_dec_l2(&ran_dec, an_apdu->msg);
+}
diff --git a/src/libmsc/neighbor_ident.c b/src/libmsc/neighbor_ident.c
new file mode 100644
index 000000000..5120e168e
--- /dev/null
+++ b/src/libmsc/neighbor_ident.c
@@ -0,0 +1,191 @@
+/* Manage identity of neighboring BSS cells for inter-MSC handover. */
+/*
+ * (C) 2018-2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * 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 <errno.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <osmocom/msc/neighbor_ident.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/cell_id_list.h>
+
+/* XXX greater than or equal to IPA_STRING_MAX (libosmocore) and MAX_PC_STR_LEN (libosmo-sccp). */
+#define NEIGHBOR_IDENT_ADDR_STRING_MAX 64
+
+static struct gsm_network *gsmnet;
+
+void neighbor_ident_init(struct gsm_network *net)
+{
+ gsmnet = net;
+ INIT_LLIST_HEAD(&gsmnet->neighbor_ident_list);
+}
+
+int msc_ipa_name_from_str(struct msc_ipa_name *min, const char *name)
+{
+ int rc = osmo_strlcpy(min->buf, name, sizeof(min->buf));
+ if (rc >= sizeof(min->buf)) {
+ min->len = 0;
+ return -1;
+ }
+ min->len = rc;
+ return 0;
+}
+
+int msc_ipa_name_cmp(const struct msc_ipa_name *a, const struct msc_ipa_name *b)
+{
+ size_t cmp_len;
+ int rc;
+ if (a == b)
+ return 0;
+ if (!a || !b)
+ return a ? 1 : -1;
+ cmp_len = OSMO_MIN(sizeof(a->buf), OSMO_MIN(a->len, b->len));
+ if (!cmp_len)
+ rc = 0;
+ else
+ rc = memcmp(a->buf, b->buf, cmp_len);
+ if (rc)
+ return rc;
+ if (a->len < b->len)
+ return -1;
+ if (a->len > b->len)
+ return 1;
+ return 0;
+}
+
+const char *neighbor_ident_addr_name(const struct neighbor_ident_addr *nia)
+{
+ static char buf[128];
+ struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
+
+ OSMO_STRBUF_PRINTF(sb, "%s-", osmo_rat_type_name(nia->ran_type));
+
+ switch (nia->type) {
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ OSMO_STRBUF_PRINTF(sb, "localRAN-%s", nia->local_ran_peer_pc_str);
+ break;
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ OSMO_STRBUF_PRINTF(sb, "remoteMSC-");
+ OSMO_STRBUF_APPEND_NOLEN(sb, osmo_escape_str_buf2, nia->remote_msc_ipa_name.buf,
+ nia->remote_msc_ipa_name.len);
+ break;
+ default:
+ return NULL;
+ }
+
+ return buf;
+}
+
+const struct neighbor_ident_entry *neighbor_ident_add(struct llist_head *ni_list,
+ const struct neighbor_ident_addr *nia,
+ const struct gsm0808_cell_id *cid)
+{
+ struct neighbor_ident_entry *nie;
+
+ if (!ni_list)
+ return NULL;
+
+ nie = (struct neighbor_ident_entry*)neighbor_ident_find_by_addr(ni_list, nia);
+ if (!nie) {
+ nie = talloc_zero(gsmnet, struct neighbor_ident_entry);
+ OSMO_ASSERT(nie);
+ *nie = (struct neighbor_ident_entry){
+ .addr = *nia,
+ };
+ INIT_LLIST_HEAD(&nie->cell_ids);
+ llist_add_tail(&nie->entry, ni_list);
+ }
+
+ cell_id_list_add_cell(nie, &nie->cell_ids, cid);
+ return nie;
+}
+
+const struct neighbor_ident_entry *neighbor_ident_find_by_cell(const struct llist_head *ni_list,
+ enum osmo_rat_type ran_type,
+ const struct gsm0808_cell_id *cell_id)
+{
+ struct neighbor_ident_entry *e;
+ llist_for_each_entry(e, ni_list, entry) {
+ if (ran_type != OSMO_RAT_UNKNOWN) {
+ if (e->addr.ran_type != ran_type)
+ continue;
+ }
+
+ if (!cell_id_list_find(&e->cell_ids, cell_id, 0, false))
+ continue;
+ return e;
+ }
+ return NULL;
+}
+
+const struct neighbor_ident_entry *neighbor_ident_find_by_addr(const struct llist_head *ni_list,
+ const struct neighbor_ident_addr *nia)
+{
+ struct neighbor_ident_entry *e;
+
+ llist_for_each_entry(e, ni_list, entry) {
+ if (nia->ran_type != OSMO_RAT_UNKNOWN
+ && e->addr.ran_type != nia->ran_type)
+ continue;
+
+ if (e->addr.type != nia->type)
+ continue;
+
+ switch (e->addr.type) {
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ if (strcmp(e->addr.local_ran_peer_pc_str, nia->local_ran_peer_pc_str))
+ continue;
+ break;
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ if (msc_ipa_name_cmp(&e->addr.remote_msc_ipa_name, &nia->remote_msc_ipa_name))
+ continue;
+ break;
+ default:
+ continue;
+ }
+
+ return e;
+ }
+
+ return NULL;
+}
+
+void neighbor_ident_del(const struct neighbor_ident_entry *nie)
+{
+ struct neighbor_ident_entry *e = (struct neighbor_ident_entry*)nie;
+ llist_del(&e->entry);
+ talloc_free(e);
+}
+
+void neighbor_ident_clear(struct llist_head *ni_list)
+{
+ struct neighbor_ident_entry *nie;
+ while ((nie = llist_first_entry_or_null(ni_list, struct neighbor_ident_entry, entry)))
+ neighbor_ident_del(nie);
+}
diff --git a/src/libmsc/neighbor_ident_vty.c b/src/libmsc/neighbor_ident_vty.c
new file mode 100644
index 000000000..a73010632
--- /dev/null
+++ b/src/libmsc/neighbor_ident_vty.c
@@ -0,0 +1,421 @@
+/* Quagga VTY implementation to manage identity of neighboring BSS cells for inter-BSC handover. */
+/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/osmo_ss7.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <osmocom/msc/vty.h>
+#include <osmocom/msc/neighbor_ident.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/ran_infra.h>
+#include <osmocom/msc/cell_id_list.h>
+
+#define NEIGHBOR_ADD_CMD "neighbor"
+#define NEIGHBOR_ADD_DOC "Add Handover target configuration\n"
+
+#define NEIGHBOR_DEL_CMD "no neighbor"
+#define NEIGHBOR_DEL_DOC NO_STR "Remove Handover target\n"
+
+#define NEIGHBOR_SHOW_CMD "show neighbor"
+#define NEIGHBOR_SHOW_DOC SHOW_STR "Show Handover targets\n"
+
+#define RAN_TYPE_PARAMS "(a|iu)"
+#define RAN_TYPE_DOC "Neighbor on GERAN-A\n" "Neighbor on UTRAN-Iu\n"
+
+#define RAN_PC_TOKEN "ran-pc"
+#define MSC_IPA_NAME_TOKEN "msc-ipa-name"
+#define HO_TARGET_PARAMS "("RAN_PC_TOKEN"|"MSC_IPA_NAME_TOKEN") RAN_PC_OR_MSC_IPA_NAME"
+#define HO_TARGET_DOC "SCCP point code of RAN peer\n" "GSUP IPA name of target MSC\n" "Point code or MSC IPA name value\n"
+
+#define LAC_PARAMS "lac <0-65535>"
+#define LAC_ARGC 1
+#define LAC_DOC "Handover target cell by LAC\n" "LAC\n"
+
+#define LAC_CI_PARAMS "lac-ci <0-65535> <0-65535>"
+#define LAC_CI_ARGC 2
+#define LAC_CI_DOC "Handover target cell by LAC and CI\n" "LAC\n" "CI\n"
+
+#define CGI_PARAMS "cgi <0-999> <0-999> <0-65535> <0-65535>"
+#define CGI_ARGC 4
+#define CGI_DOC "Handover target cell by Cell-Global Identifier (MCC, MNC, LAC, CI)\n" "MCC\n" "MNC\n" "LAC\n" "CI\n"
+
+static struct gsm_network *gsmnet = NULL;
+
+static void write_neighbor_ident_cell(struct vty *vty, const struct neighbor_ident_entry *e,
+ const struct gsm0808_cell_id *cid)
+{
+ vty_out(vty, " " NEIGHBOR_ADD_CMD " ");
+
+ switch (e->addr.ran_type) {
+ case OSMO_RAT_GERAN_A:
+ vty_out(vty, "a");
+ break;
+ case OSMO_RAT_UTRAN_IU:
+ vty_out(vty, "iu");
+ break;
+ default:
+ vty_out(vty, "<Unsupported-RAN-type>");
+ break;
+ }
+
+ vty_out(vty, " ");
+
+ switch (cid->id_discr) {
+ case CELL_IDENT_LAC:
+ vty_out(vty, "lac %u", cid->id.lac);
+ break;
+ case CELL_IDENT_LAC_AND_CI:
+ vty_out(vty, "lac-ci %u %u",
+ cid->id.lac_and_ci.lac,
+ cid->id.lac_and_ci.ci);
+ break;
+ case CELL_IDENT_WHOLE_GLOBAL:
+ vty_out(vty, "cgi %s %s %u %u",
+ osmo_mcc_name(cid->id.global.lai.plmn.mcc),
+ osmo_mnc_name(cid->id.global.lai.plmn.mnc, cid->id.global.lai.plmn.mnc_3_digits),
+ cid->id.global.lai.lac,
+ cid->id.global.cell_identity);
+ break;
+ default:
+ vty_out(vty, "<Unsupported-Cell-Identity>");
+ break;
+ }
+
+ vty_out(vty, " ");
+
+ switch (e->addr.type) {
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ vty_out(vty, RAN_PC_TOKEN " %s", e->addr.local_ran_peer_pc_str);
+ break;
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ vty_out(vty, MSC_IPA_NAME_TOKEN " %s", osmo_escape_str(e->addr.remote_msc_ipa_name.buf,
+ e->addr.remote_msc_ipa_name.len));
+ break;
+ default:
+ vty_out(vty, "<Unsupported-target-type>");
+ break;
+ }
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void write_neighbor_ident_entry(struct vty *vty, const struct neighbor_ident_entry *e)
+{
+ struct cell_id_list_entry *le;
+
+ llist_for_each_entry(le, &e->cell_ids, entry) {
+ write_neighbor_ident_cell(vty, e, &le->cell_id);
+ }
+
+}
+
+static void write_neighbor_ident_entry_by_cell(struct vty *vty, const struct neighbor_ident_entry *e,
+ const struct gsm0808_cell_id *cid)
+{
+ struct cell_id_list_entry *le;
+
+ llist_for_each_entry(le, &e->cell_ids, entry) {
+ if (!gsm0808_cell_ids_match(&le->cell_id, cid, false))
+ continue;
+ write_neighbor_ident_cell(vty, e, &le->cell_id);
+ }
+
+}
+
+void neighbor_ident_vty_write(struct vty *vty)
+{
+ const struct neighbor_ident_entry *e;
+
+ llist_for_each_entry(e, &gsmnet->neighbor_ident_list, entry) {
+ write_neighbor_ident_entry(vty, e);
+ }
+}
+
+void neighbor_ident_vty_write_by_ran_type(struct vty *vty, enum osmo_rat_type ran_type)
+{
+ const struct neighbor_ident_entry *e;
+
+ llist_for_each_entry(e, &gsmnet->neighbor_ident_list, entry) {
+ if (e->addr.ran_type != ran_type)
+ continue;
+ write_neighbor_ident_entry(vty, e);
+ }
+}
+
+void neighbor_ident_vty_write_by_cell(struct vty *vty, enum osmo_rat_type ran_type, const struct gsm0808_cell_id *cid)
+{
+ struct neighbor_ident_entry *e;
+
+ llist_for_each_entry(e, &gsmnet->neighbor_ident_list, entry) {
+ if (ran_type != OSMO_RAT_UNKNOWN
+ && e->addr.ran_type != ran_type)
+ continue;
+ write_neighbor_ident_entry_by_cell(vty, e, cid);
+ }
+}
+
+static struct gsm0808_cell_id *parse_lac(struct vty *vty, const char **argv)
+{
+ static struct gsm0808_cell_id cell_id;
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_LAC,
+ .id.lac = atoi(argv[0]),
+ };
+ return &cell_id;
+}
+
+static struct gsm0808_cell_id *parse_lac_ci(struct vty *vty, const char **argv)
+{
+ static struct gsm0808_cell_id cell_id;
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_LAC_AND_CI,
+ .id.lac_and_ci = {
+ .lac = atoi(argv[0]),
+ .ci = atoi(argv[1]),
+ },
+ };
+ return &cell_id;
+}
+
+static struct gsm0808_cell_id *parse_cgi(struct vty *vty, const char **argv)
+{
+ static struct gsm0808_cell_id cell_id;
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL,
+ };
+ struct osmo_cell_global_id *cgi = &cell_id.id.global;
+ const char *mcc = argv[0];
+ const char *mnc = argv[1];
+ const char *lac = argv[2];
+ const char *ci = argv[3];
+
+ if (osmo_mcc_from_str(mcc, &cgi->lai.plmn.mcc)) {
+ vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
+ return NULL;
+ }
+
+ if (osmo_mnc_from_str(mnc, &cgi->lai.plmn.mnc, &cgi->lai.plmn.mnc_3_digits)) {
+ vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
+ return NULL;
+ }
+
+ cgi->lai.lac = atoi(lac);
+ cgi->cell_identity = atoi(ci);
+ return &cell_id;
+}
+
+static int add_neighbor(struct vty *vty, struct neighbor_ident_addr *addr, const struct gsm0808_cell_id *cell_id)
+{
+ if (!neighbor_ident_add(&gsmnet->neighbor_ident_list, addr, cell_id)) {
+ vty_out(vty, "%% Error: cannot add cell %s to neighbor %s%s",
+ gsm0808_cell_id_name(cell_id), neighbor_ident_addr_name(addr),
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+static enum osmo_rat_type parse_ran_type(struct vty *vty, const char *ran_type_str)
+{
+ if (!strcmp(ran_type_str, "a"))
+ return OSMO_RAT_GERAN_A;
+ else if (!strcmp(ran_type_str, "iu"))
+ return OSMO_RAT_UTRAN_IU;
+ vty_out(vty, "%% Error: cannot parse RAN type argument %s%s",
+ osmo_quote_str(ran_type_str, -1), VTY_NEWLINE);
+ return OSMO_RAT_UNKNOWN;
+}
+
+static enum msc_neighbor_type parse_target_type(struct vty *vty, const char *target_type_str)
+{
+ if (osmo_str_startswith(RAN_PC_TOKEN, target_type_str))
+ return MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER;
+ if (osmo_str_startswith(MSC_IPA_NAME_TOKEN, target_type_str))
+ return MSC_NEIGHBOR_TYPE_REMOTE_MSC;
+ vty_out(vty, "%% Unknown Handover target type: %s%s\n",
+ osmo_quote_str(target_type_str, -1), VTY_NEWLINE);
+ return MSC_NEIGHBOR_TYPE_NONE;
+}
+
+static int parse_ho_target_addr(struct vty *vty,
+ struct neighbor_ident_addr *nia,
+ enum osmo_rat_type ran_type,
+ const char **argv)
+{
+ const char *target_type_str = argv[0];
+ const char *arg_str = argv[1];
+ int rc;
+
+ *nia = (struct neighbor_ident_addr){
+ .type = parse_target_type(vty, target_type_str),
+ .ran_type = ran_type,
+ };
+ if (nia->ran_type == OSMO_RAT_UNKNOWN)
+ return -1;
+
+ switch (nia->type) {
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ rc = osmo_strlcpy(nia->local_ran_peer_pc_str, arg_str, sizeof(nia->local_ran_peer_pc_str));
+ if (rc < 1 || rc >= sizeof(nia->local_ran_peer_pc_str)) {
+ vty_out(vty, "%% Invalid RAN peer point-code string: %s%s", osmo_quote_str(arg_str, -1), VTY_NEWLINE);
+ return -1;
+ }
+ return 0;
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ if (msc_ipa_name_from_str(&nia->remote_msc_ipa_name, arg_str)) {
+ vty_out(vty, "%% Invalid MSC IPA name: %s%s", osmo_quote_str(arg_str, -1), VTY_NEWLINE);
+ return -1;
+ }
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+#define DEFUN_CELL(id_name, ID_NAME) \
+ \
+DEFUN(cfg_neighbor_add_##id_name, cfg_neighbor_add_##id_name##_cmd, \
+ NEIGHBOR_ADD_CMD " "RAN_TYPE_PARAMS " " ID_NAME##_PARAMS " " HO_TARGET_PARAMS, \
+ NEIGHBOR_ADD_DOC RAN_TYPE_DOC ID_NAME##_DOC HO_TARGET_DOC) \
+{ \
+ struct neighbor_ident_addr addr; \
+ if (parse_ho_target_addr(vty, &addr, \
+ parse_ran_type(vty, argv[0]), \
+ argv + 1 + ID_NAME##_ARGC)) \
+ return CMD_WARNING; \
+ return add_neighbor(vty, &addr, parse_##id_name(vty, argv + 1)); \
+} \
+ \
+DEFUN(show_neighbor_ran_##id_name, show_neighbor_ran_##id_name##_cmd, \
+ NEIGHBOR_SHOW_CMD " " RAN_TYPE_PARAMS " " ID_NAME##_PARAMS, \
+ NEIGHBOR_SHOW_DOC RAN_TYPE_DOC ID_NAME##_DOC RAN_TYPE_DOC) \
+{ \
+ neighbor_ident_vty_write_by_cell(vty, \
+ parse_ran_type(vty, argv[0]), \
+ parse_##id_name(vty, argv + 1)); \
+ return CMD_SUCCESS; \
+} \
+ \
+DEFUN(show_neighbor_##id_name, show_neighbor_##id_name##_cmd, \
+ NEIGHBOR_SHOW_CMD " "ID_NAME##_PARAMS, \
+ NEIGHBOR_SHOW_DOC ID_NAME##_DOC) \
+{ \
+ neighbor_ident_vty_write_by_cell(vty, OSMO_RAT_UNKNOWN, parse_##id_name(vty, argv)); \
+ return CMD_SUCCESS; \
+}
+
+DEFUN_CELL(lac, LAC)
+DEFUN_CELL(lac_ci, LAC_CI)
+DEFUN_CELL(cgi, CGI)
+
+static int del_by_addr(struct vty *vty, const struct neighbor_ident_addr *addr)
+{
+ const struct neighbor_ident_entry *e = neighbor_ident_find_by_addr(&gsmnet->neighbor_ident_list, addr);
+
+ if (!e) {
+ vty_out(vty, "%% Cannot remove, no such neighbor: %s%s",
+ neighbor_ident_addr_name(addr), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ neighbor_ident_del(e);
+ vty_out(vty, "%% Removed neighbor %s%s", neighbor_ident_addr_name(addr), VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_del_neighbor, cfg_del_neighbor_cmd,
+ NEIGHBOR_DEL_CMD " " RAN_TYPE_PARAMS " "HO_TARGET_PARAMS,
+ NEIGHBOR_DEL_DOC RAN_TYPE_DOC HO_TARGET_DOC)
+{
+ struct neighbor_ident_addr addr;
+ if (parse_ho_target_addr(vty, &addr,
+ parse_ran_type(vty, argv[0]),
+ argv + 1))
+ return CMD_WARNING;
+
+ return del_by_addr(vty, &addr);
+}
+
+DEFUN(show_neighbor_all, show_neighbor_all_cmd,
+ NEIGHBOR_SHOW_CMD,
+ NEIGHBOR_SHOW_DOC)
+{
+ neighbor_ident_vty_write(vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_neighbor_ran, show_neighbor_ran_cmd,
+ NEIGHBOR_SHOW_CMD " " RAN_TYPE_PARAMS,
+ NEIGHBOR_SHOW_DOC RAN_TYPE_DOC)
+{
+ neighbor_ident_vty_write_by_ran_type(vty, parse_ran_type(vty, argv[0]));
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_neighbor, show_neighbor_cmd,
+ NEIGHBOR_SHOW_CMD " "RAN_TYPE_PARAMS " " HO_TARGET_PARAMS,
+ NEIGHBOR_SHOW_DOC RAN_TYPE_DOC HO_TARGET_DOC)
+{
+ const struct neighbor_ident_entry *e;
+ struct neighbor_ident_addr addr;
+ if (parse_ho_target_addr(vty, &addr,
+ parse_ran_type(vty, argv[0]),
+ argv + 1))
+ return CMD_WARNING;
+
+ e = neighbor_ident_find_by_addr(&gsmnet->neighbor_ident_list, &addr);
+ if (e)
+ write_neighbor_ident_entry(vty, e);
+ else
+ vty_out(vty, "%% No such neighbor target%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+void neighbor_ident_vty_init(struct gsm_network *net)
+{
+ gsmnet = net;
+
+ install_element(MSC_NODE, &cfg_neighbor_add_lac_cmd);
+ install_element(MSC_NODE, &cfg_neighbor_add_lac_ci_cmd);
+ install_element(MSC_NODE, &cfg_neighbor_add_cgi_cmd);
+ install_element(MSC_NODE, &cfg_del_neighbor_cmd);
+ install_element_ve(&show_neighbor_all_cmd);
+ install_element_ve(&show_neighbor_cmd);
+ install_element_ve(&show_neighbor_ran_cmd);
+
+ install_element_ve(&show_neighbor_ran_lac_cmd);
+ install_element_ve(&show_neighbor_ran_lac_ci_cmd);
+ install_element_ve(&show_neighbor_ran_cgi_cmd);
+
+ install_element_ve(&show_neighbor_lac_cmd);
+ install_element_ve(&show_neighbor_lac_ci_cmd);
+ install_element_ve(&show_neighbor_cgi_cmd);
+}
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
deleted file mode 100644
index 5c6f0aa2e..000000000
--- a/src/libmsc/osmo_msc.c
+++ /dev/null
@@ -1,227 +0,0 @@
-/* main MSC management code... */
-
-/*
- * (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2010 by On-Waves
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include <osmocom/msc/debug.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/msc/db.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/msc/a_iface.h>
-#include <osmocom/msc/gsm_04_08.h>
-#include <osmocom/msc/gsm_04_11.h>
-#include <osmocom/msc/msc_mgcp.h>
-
-#include "../../bscconfig.h"
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#else
-#include <osmocom/msc/iu_dummy.h>
-#endif
-
-struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv)
-{
- struct gsm_network *net;
-
- net = talloc_zero(ctx, struct gsm_network);
- if (!net)
- return NULL;
-
- net->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
-
- /* Permit a compile-time default of A5/3 and A5/1 */
- net->a5_encryption_mask = (1 << 3) | (1 << 1);
-
- /* Use 30 min periodic update interval as sane default */
- net->t3212 = 5;
-
- net->mncc_guard_timeout = 180;
- net->ncss_guard_timeout = 30;
-
- net->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT;
-
- INIT_LLIST_HEAD(&net->trans_list);
- INIT_LLIST_HEAD(&net->upqueue);
- INIT_LLIST_HEAD(&net->ran_conns);
-
- /* init statistics */
- net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
- if (!net->msc_ctrs) {
- talloc_free(net);
- return NULL;
- }
- net->active_calls = osmo_counter_alloc("msc.active_calls");
- net->active_nc_ss = osmo_counter_alloc("msc.active_nc_ss");
-
- net->mncc_recv = mncc_recv;
-
- INIT_LLIST_HEAD(&net->a.bscs);
-
- return net;
-}
-
-void gsm_network_set_mncc_sock_path(struct gsm_network *net, const char *mncc_sock_path)
-{
- if (net->mncc_sock_path)
- talloc_free(net->mncc_sock_path);
- net->mncc_sock_path = mncc_sock_path ? talloc_strdup(net, mncc_sock_path) : NULL;
-}
-
-/* Receive a SAPI-N-REJECT from BSC */
-void ran_conn_sapi_n_reject(struct ran_conn *conn, int dlci)
-{
- int sapi = dlci & 0x7;
-
- if (sapi == UM_SAPI_SMS)
- gsm411_sapi_n_reject(conn);
-}
-
-/* receive a Level 3 Complete message.
- * Ownership of the conn is completely passed to the conn FSM, i.e. for both acceptance and rejection,
- * the conn FSM shall decide when to release this conn. It may already be discarded before this exits. */
-void ran_conn_compl_l3(struct ran_conn *conn,
- struct msgb *msg, uint16_t chosen_channel)
-{
- ran_conn_get(conn, RAN_CONN_USE_COMPL_L3);
- gsm0408_dispatch(conn, msg);
- ran_conn_put(conn, RAN_CONN_USE_COMPL_L3);
-}
-
-/* Receive a DTAP message from BSC */
-void ran_conn_dtap(struct ran_conn *conn, struct msgb *msg)
-{
- ran_conn_get(conn, RAN_CONN_USE_DTAP);
- gsm0408_dispatch(conn, msg);
-
- ran_conn_put(conn, RAN_CONN_USE_DTAP);
-}
-
-/* Receive an ASSIGNMENT COMPLETE from BSC */
-void msc_assign_compl(struct ran_conn *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 */
-void ran_conn_assign_fail(struct ran_conn *conn, uint8_t cause, uint8_t *rr_cause)
-{
- LOGPFSMSL(conn->fi, DRR, LOGL_ERROR, "Assignment Failure: cause=%u rr_cause=%u.\n",
- cause, rr_cause ? *rr_cause : 0);
- msc_mgcp_ass_fail(conn);
-}
-
-/* Receive a CLASSMARK CHANGE from BSC */
-void ran_conn_classmark_chg(struct ran_conn *conn,
- const uint8_t *cm2, uint8_t cm2_len,
- const uint8_t *cm3, uint8_t cm3_len)
-{
- struct gsm_classmark *cm;
-
- if (!conn->vsub)
- cm = &conn->temporary_classmark;
- else
- cm = &conn->vsub->classmark;
-
- if (cm2 && cm2_len) {
- if (cm2_len > sizeof(cm->classmark2)) {
- LOGP(DRR, LOGL_NOTICE, "%s: classmark2 is %u bytes, truncating at %zu bytes\n",
- vlr_subscr_name(conn->vsub), cm2_len, sizeof(cm->classmark2));
- cm2_len = sizeof(cm->classmark2);
- }
- cm->classmark2_len = cm2_len;
- memcpy(cm->classmark2, cm2, cm2_len);
- }
- if (cm3 && cm3_len) {
- if (cm3_len > sizeof(cm->classmark3)) {
- LOGP(DRR, LOGL_NOTICE, "%s: classmark3 is %u bytes, truncating at %zu bytes\n",
- vlr_subscr_name(conn->vsub), cm3_len, sizeof(cm->classmark3));
- cm3_len = sizeof(cm->classmark3);
- }
- cm->classmark3_len = cm3_len;
- memcpy(cm->classmark3, cm3, cm3_len);
- }
-
- /* bump subscr conn FSM in case it is waiting for a Classmark Update */
- if (conn->fi->state == RAN_CONN_S_WAIT_CLASSMARK_UPDATE)
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_CLASSMARK_UPDATE, NULL);
-}
-
-/* Receive a CIPHERING MODE COMPLETE from BSC */
-void ran_conn_cipher_mode_compl(struct ran_conn *conn, struct msgb *msg, uint8_t alg_id)
-{
- struct vlr_ciph_result ciph_res = { .cause = VLR_CIPH_REJECT };
-
- if (!conn) {
- LOGP(DRR, LOGL_ERROR, "invalid: rx Ciphering Mode Complete on NULL conn\n");
- return;
- }
- if (!conn->vsub) {
- LOGP(DRR, LOGL_ERROR, "invalid: rx Ciphering Mode Complete for NULL subscr\n");
- return;
- }
-
- DEBUGP(DRR, "%s: CIPHERING MODE COMPLETE\n", vlr_subscr_name(conn->vsub));
-
- if (msg) {
- struct gsm48_hdr *gh = msgb_l3(msg);
- unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
- struct tlv_parsed tp;
- uint8_t mi_type;
-
- if (!gh) {
- LOGP(DRR, LOGL_ERROR, "invalid: msgb without l3 header\n");
- return;
- }
-
- tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
-
- /* bearer capability */
- if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) {
- mi_type = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)[0] & GSM_MI_TYPE_MASK;
- if (mi_type == GSM_MI_TYPE_IMEISV
- && TLVP_LEN(&tp, GSM48_IE_MOBILE_ID) > 0) {
- gsm48_mi_to_string(ciph_res.imeisv, sizeof(ciph_res.imeisv),
- TLVP_VAL(&tp, GSM48_IE_MOBILE_ID),
- TLVP_LEN(&tp, GSM48_IE_MOBILE_ID));
- }
- }
- }
-
- conn->geran_encr.alg_id = alg_id;
-
- ciph_res.cause = VLR_CIPH_COMPL;
- vlr_subscr_rx_ciph_res(conn->vsub, &ciph_res);
-}
-
-/* Receive a CLEAR REQUEST from BSC */
-int ran_conn_clear_request(struct ran_conn *conn, uint32_t cause)
-{
- ran_conn_close(conn, cause);
- return 1;
-}
-
-void msc_stop_paging(struct vlr_subscr *vsub)
-{
- DEBUGP(DPAG, "Paging can stop for %s\n", vlr_subscr_name(vsub));
- /* tell BSCs and RNCs to stop paging? How? */
-}
diff --git a/src/libmsc/paging.c b/src/libmsc/paging.c
new file mode 100644
index 000000000..5baa0dca0
--- /dev/null
+++ b/src/libmsc/paging.c
@@ -0,0 +1,183 @@
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/msc/gsm_data.h>
+#include <osmocom/msc/paging.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/sgs_iface.h>
+#include <osmocom/msc/signal.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/transaction.h>
+
+#define LOG_PAGING(vsub, paging_request, level, fmt, args ...) \
+ LOGP(DPAG, level, "Paging: %s%s%s: " fmt, \
+ vlr_subscr_name(vsub), paging_request ? " for " : "", paging_request ? (paging_request)->label : "", ## args)
+
+#define VSUB_USE_PAGING "Paging"
+
+const struct value_string paging_cause_names[] = {
+ { PAGING_CAUSE_CALL_CONVERSATIONAL, "CALL_CONVERSATIONAL" },
+ { PAGING_CAUSE_CALL_STREAMING, "CALL_STREAMING" },
+ { PAGING_CAUSE_CALL_INTERACTIVE, "CALL_INTERACTIVE" },
+ { PAGING_CAUSE_CALL_BACKGROUND, "CALL_BACKGROUND" },
+ { PAGING_CAUSE_SIGNALLING_LOW_PRIO, "SIGNALLING_LOW_PRIO" },
+ { PAGING_CAUSE_SIGNALLING_HIGH_PRIO, "SIGNALLING_HIGH_PRIO" },
+ { PAGING_CAUSE_UNSPECIFIED, "UNSPECIFIED" },
+ {}
+};
+
+static void paging_response_timer_cb(void *data)
+{
+ struct vlr_subscr *vsub = data;
+ paging_expired(vsub);
+}
+
+/* Execute a paging on the currently active RAN. Returns the number of
+ * delivered paging requests or -EINVAL in case of failure. */
+static int msc_paging_request(struct paging_request *pr, struct vlr_subscr *vsub)
+{
+ struct gsm_network *net = vsub->vlr->user_ctx;
+
+ /* The subscriber was last seen in subscr->lac. Find out which
+ * BSCs/RNCs are responsible and send them a paging request via open
+ * SCCP connections (if any). */
+ switch (vsub->cs.attached_via_ran) {
+ case OSMO_RAT_GERAN_A:
+ return ran_peers_down_paging(net->a.sri, CELL_IDENT_LAC, vsub, pr->cause);
+ case OSMO_RAT_UTRAN_IU:
+ return ran_peers_down_paging(net->iu.sri, CELL_IDENT_LAC, vsub, pr->cause);
+ case OSMO_RAT_EUTRAN_SGS:
+ return sgs_iface_tx_paging(vsub, sgs_serv_ind_from_paging_cause(pr->cause));
+ default:
+ break;
+ }
+
+ LOG_PAGING(vsub, pr, LOGL_ERROR, " Cannot page, subscriber not attached\n");
+ return -EINVAL;
+}
+
+struct paging_request *paging_request_start(struct vlr_subscr *vsub, enum paging_cause cause,
+ paging_cb_t paging_cb, struct gsm_trans *trans,
+ const char *label)
+{
+ int rc;
+ struct paging_request *pr;
+ struct gsm_network *net = vsub->vlr->user_ctx;
+
+ pr = talloc_zero(vsub, struct paging_request);
+ OSMO_ASSERT(pr);
+ *pr = (struct paging_request){
+ .label = label,
+ .cause = cause,
+ .paging_cb = paging_cb,
+ .trans = trans,
+ };
+
+ if (vsub->cs.is_paging) {
+ LOG_PAGING(vsub, pr, LOGL_DEBUG, "Already paging, not starting another request\n");
+ } else {
+ LOG_PAGING(vsub, pr, LOGL_DEBUG, "Starting paging\n");
+
+ rc = msc_paging_request(pr, vsub);
+ if (rc <= 0) {
+ LOG_PAGING(vsub, pr, LOGL_ERROR, "Starting paging failed (rc=%d)\n", rc);
+ talloc_free(pr);
+ return NULL;
+ }
+
+ /* reduced on the first paging callback */
+ vlr_subscr_get(vsub, VSUB_USE_PAGING);
+ vsub->cs.is_paging = true;
+ osmo_timer_setup(&vsub->cs.paging_response_timer, paging_response_timer_cb, vsub);
+ osmo_timer_schedule(&vsub->cs.paging_response_timer, net->paging_response_timer, 0);
+ }
+
+ llist_add_tail(&pr->entry, &vsub->cs.requests);
+
+ return pr;
+}
+
+void paging_request_remove(struct paging_request *pr)
+{
+ struct gsm_trans *trans = pr->trans;
+ struct vlr_subscr *vsub = trans ? trans->vsub : NULL;
+ LOG_PAGING(vsub, pr, LOGL_DEBUG, "Removing Paging Request\n");
+
+ if (pr->trans && pr->trans->paging_request == pr)
+ pr->trans->paging_request = NULL;
+
+ llist_del(&pr->entry);
+ talloc_free(pr);
+}
+
+static void paging_concludes(struct vlr_subscr *vsub, struct msc_a *msc_a)
+{
+ struct paging_request *pr, *pr_next;
+ struct paging_signal_data sig_data;
+
+ osmo_timer_del(&vsub->cs.paging_response_timer);
+
+ llist_for_each_entry_safe(pr, pr_next, &vsub->cs.requests, entry) {
+ struct gsm_trans *trans = pr->trans;
+ paging_cb_t paging_cb = pr->paging_cb;
+
+ LOG_PAGING(vsub, pr, LOGL_DEBUG, "Paging Response action (%s)%s\n",
+ msc_a ? "success" : "expired",
+ paging_cb ? "" : " (no action defined)");
+
+ /* Remove the paging request before the paging_cb could deallocate e.g. the trans */
+ paging_request_remove(pr);
+ pr = NULL;
+
+ if (paging_cb)
+ paging_cb(msc_a, trans);
+ }
+
+ /* Inform parts of the system we don't know */
+ sig_data = (struct paging_signal_data){
+ .vsub = vsub,
+ .msc_a = msc_a,
+ };
+ osmo_signal_dispatch(SS_PAGING, msc_a ? S_PAGING_SUCCEEDED : S_PAGING_EXPIRED, &sig_data);
+
+ /* balanced with the moment we start paging */
+ if (vsub->cs.is_paging) {
+ vsub->cs.is_paging = false;
+ vlr_subscr_put(vsub, VSUB_USE_PAGING);
+ }
+
+ /* Handling of the paging requests has usually added transactions, which keep the msc_a connection active. If
+ * there are none, then this probably marks release of the connection. */
+ if (msc_a)
+ msc_a_put(msc_a, MSC_A_USE_PAGING_RESPONSE);
+}
+
+void paging_response(struct msc_a *msc_a)
+{
+ paging_concludes(msc_a_vsub(msc_a), msc_a);
+}
+
+void paging_expired(struct vlr_subscr *vsub)
+{
+ paging_concludes(vsub, NULL);
+}
diff --git a/src/libmsc/ran_conn.c b/src/libmsc/ran_conn.c
index 8ad183e0e..8418c9eb5 100644
--- a/src/libmsc/ran_conn.c
+++ b/src/libmsc/ran_conn.c
@@ -30,883 +30,139 @@
#include <osmocom/msc/debug.h>
#include <osmocom/msc/transaction.h>
#include <osmocom/msc/signal.h>
-#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/sgs_iface.h>
-#include <osmocom/msc/iucs.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/ran_infra.h>
+#include <osmocom/msc/msub.h>
-#include "../../bscconfig.h"
-#ifdef BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#else
-#include <osmocom/msc/iu_dummy.h>
-#endif
-
-#define RAN_CONN_TIMEOUT 5 /* seconds */
-
-static const struct value_string ran_conn_fsm_event_names[] = {
- OSMO_VALUE_STRING(RAN_CONN_E_COMPLETE_LAYER_3),
- OSMO_VALUE_STRING(RAN_CONN_E_CLASSMARK_UPDATE),
- OSMO_VALUE_STRING(RAN_CONN_E_ACCEPTED),
- OSMO_VALUE_STRING(RAN_CONN_E_COMMUNICATING),
- OSMO_VALUE_STRING(RAN_CONN_E_RELEASE_WHEN_UNUSED),
- OSMO_VALUE_STRING(RAN_CONN_E_MO_CLOSE),
- OSMO_VALUE_STRING(RAN_CONN_E_CN_CLOSE),
- OSMO_VALUE_STRING(RAN_CONN_E_UNUSED),
- { 0, NULL }
-};
-
-static void update_counters(struct osmo_fsm_inst *fi, bool conn_accepted)
-{
- struct ran_conn *conn = fi->priv;
- switch (conn->complete_layer3_type) {
- case COMPLETE_LAYER3_LU:
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[
- conn_accepted ? MSC_CTR_LOC_UPDATE_COMPLETED
- : MSC_CTR_LOC_UPDATE_FAILED]);
- break;
- case COMPLETE_LAYER3_CM_SERVICE_REQ:
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[
- conn_accepted ? MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED
- : MSC_CTR_CM_SERVICE_REQUEST_REJECTED]);
- break;
- case COMPLETE_LAYER3_PAGING_RESP:
- rate_ctr_inc(&conn->network->msc_ctrs->ctr[
- conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED
- : MSC_CTR_PAGING_RESP_REJECTED]);
- break;
- default:
- break;
- }
-}
-
-static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_accepted)
-{
- struct ran_conn *conn = fi->priv;
-
- update_counters(fi, conn_accepted);
-
- /* Trigger transactions that we paged for */
- if (conn->complete_layer3_type == COMPLETE_LAYER3_PAGING_RESP) {
- subscr_paging_dispatch(GSM_HOOK_RR_PAGING,
- conn_accepted ? GSM_PAGING_SUCCEEDED : GSM_PAGING_EXPIRED,
- NULL, conn, conn->vsub);
- }
-
- if (conn->complete_layer3_type == COMPLETE_LAYER3_CM_SERVICE_REQ
- && conn_accepted) {
- conn->received_cm_service_request = true;
- ran_conn_get(conn, RAN_CONN_USE_CM_SERVICE);
- }
-
- if (conn_accepted)
- osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, conn->vsub);
-}
-
-static void log_close_event(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- enum gsm48_reject_value *cause = data;
- /* The close event itself is logged by the FSM. We can only add the cause value, if present. */
- if (!cause || !*cause)
- return;
- LOGPFSML(fi, LOGL_NOTICE, "Close event, cause: %s\n", gsm48_reject_value_name(*cause));
-}
-
-static void ran_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- switch (event) {
- case RAN_CONN_E_COMPLETE_LAYER_3:
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_AUTH_CIPH, RAN_CONN_TIMEOUT, 0);
- return;
-
- case RAN_CONN_E_ACCEPTED:
- evaluate_acceptance_outcome(fi, true);
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_ACCEPTED, RAN_CONN_TIMEOUT, 0);
- return;
-
- case RAN_CONN_E_MO_CLOSE:
- case RAN_CONN_E_CN_CLOSE:
- log_close_event(fi, event, data);
- evaluate_acceptance_outcome(fi, false);
- /* fall through */
- case RAN_CONN_E_UNUSED:
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
- return;
-
- default:
- OSMO_ASSERT(false);
- }
-}
-
-static void ran_conn_fsm_auth_ciph(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- /* If accepted, transition the state, all other cases mean failure. */
- switch (event) {
- case RAN_CONN_E_ACCEPTED:
- evaluate_acceptance_outcome(fi, true);
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_ACCEPTED, RAN_CONN_TIMEOUT, 0);
- return;
-
- case RAN_CONN_E_UNUSED:
- LOGPFSML(fi, LOGL_DEBUG, "Awaiting results for Auth+Ciph, overruling event %s\n",
- osmo_fsm_event_name(fi->fsm, event));
- return;
-
- case RAN_CONN_E_MO_CLOSE:
- case RAN_CONN_E_CN_CLOSE:
- log_close_event(fi, event, data);
- evaluate_acceptance_outcome(fi, false);
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
- return;
-
-
- default:
- OSMO_ASSERT(false);
- }
-}
-
-int ran_conn_classmark_request_then_cipher_mode_cmd(struct ran_conn *conn, bool umts_aka,
- bool retrieve_imeisv)
+struct ran_conn *ran_conn_create_incoming(struct ran_peer *ran_peer, uint32_t sccp_conn_id)
{
- int rc;
- conn->geran_set_cipher_mode.umts_aka = umts_aka;
- conn->geran_set_cipher_mode.retrieve_imeisv = retrieve_imeisv;
-
- rc = a_iface_tx_classmark_request(conn);
- if (rc) {
- LOGP(DMM, LOGL_ERROR, "%s: cannot send BSSMAP Classmark Request\n",
- vlr_subscr_name(conn->vsub));
- return -EIO;
- }
-
- osmo_fsm_inst_state_chg(conn->fi, RAN_CONN_S_WAIT_CLASSMARK_UPDATE, RAN_CONN_TIMEOUT, 0);
- return 0;
-}
-
-static void ran_conn_fsm_wait_classmark_update(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct ran_conn *conn = fi->priv;
- switch (event) {
- case RAN_CONN_E_CLASSMARK_UPDATE:
- /* Theoretically, this event can be used for requesting Classmark in various situations.
- * So far though, the only time we send a Classmark Request is during Ciphering. As soon
- * as more such situations arise, we need to add state to indicate what action should
- * follow after a Classmark Update is received (e.g.
- * ran_conn_classmark_request_then_cipher_mode_cmd() sets an enum value to indicate that
- * Ciphering should continue afterwards). But right now, it is accurate to always
- * continue with Ciphering: */
-
- /* During Ciphering, we needed Classmark information. The Classmark Update has come in,
- * go back into the Set Ciphering Command procedure. */
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_AUTH_CIPH, RAN_CONN_TIMEOUT, 0);
- if (ran_conn_geran_set_cipher_mode(conn, conn->geran_set_cipher_mode.umts_aka,
- conn->geran_set_cipher_mode.retrieve_imeisv)) {
- LOGPFSML(fi, LOGL_ERROR,
- "Sending Cipher Mode Command failed, aborting attach\n");
- vlr_subscr_cancel_attach_fsm(conn->vsub, OSMO_FSM_TERM_ERROR,
- GSM48_REJECT_NETWORK_FAILURE);
- }
- return;
+ struct ran_conn *conn;
- case RAN_CONN_E_UNUSED:
- LOGPFSML(fi, LOGL_DEBUG, "Awaiting results for Auth+Ciph, overruling event %s\n",
- osmo_fsm_event_name(fi->fsm, event));
- return;
+ conn = talloc(ran_peer, struct ran_conn);
+ OSMO_ASSERT(conn);
- case RAN_CONN_E_MO_CLOSE:
- case RAN_CONN_E_CN_CLOSE:
- log_close_event(fi, event, data);
- evaluate_acceptance_outcome(fi, false);
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
- return;
+ *conn = (struct ran_conn){
+ .ran_peer = ran_peer,
+ .sccp_conn_id = sccp_conn_id,
+ };
- default:
- OSMO_ASSERT(false);
- }
+ llist_add(&conn->entry, &ran_peer->sri->ran_conns);
+ return conn;
}
-static bool ran_conn_fsm_has_active_transactions(struct osmo_fsm_inst *fi)
+struct ran_conn *ran_conn_create_outgoing(struct ran_peer *ran_peer)
{
- struct ran_conn *conn = fi->priv;
- struct gsm_trans *trans;
-
- if (conn->silent_call) {
- LOGPFSML(fi, LOGL_DEBUG, "%s: silent call still active\n", __func__);
- return true;
- }
+ /* FIXME use method being developed in gerrit id Ifd55c6b7ed2558ff072042079cf45f5068a971de */
+ static uint32_t next_outgoing_conn_id = 2342;
+ uint32_t conn_id = 0;
+ int attempts = 1000;
+ bool already_used = true;
+ while (attempts--) {
+ struct ran_conn *conn;
- if (conn->received_cm_service_request) {
- LOGPFSML(fi, LOGL_DEBUG, "%s: still awaiting first request after a CM Service Request\n",
- __func__);
- return true;
- }
+ conn_id = next_outgoing_conn_id;
+ next_outgoing_conn_id++;
- if (conn->vsub && !llist_empty(&conn->vsub->cs.requests)) {
- struct subscr_request *sr;
- if (!log_check_level(fi->fsm->log_subsys, LOGL_DEBUG)) {
- llist_for_each_entry(sr, &conn->vsub->cs.requests, entry) {
- LOGPFSML(fi, LOGL_DEBUG, "%s: still active: %s\n",
- __func__, sr->label);
+ already_used = false;
+ llist_for_each_entry(conn, &ran_peer->sri->ran_conns, entry) {
+ if (conn->sccp_conn_id == conn_id) {
+ already_used = true;
+ break;
}
}
- return true;
- }
-
- if ((trans = trans_has_conn(conn))) {
- LOGPFSML(fi, LOGL_DEBUG,
- "%s: connection still has active transaction: %s\n",
- __func__, gsm48_pdisc_name(trans->protocol));
- return true;
- }
-
- return false;
-}
-
-static void ran_conn_fsm_accepted_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- struct ran_conn *conn = fi->priv;
-
- /* Stop Location Update expiry for this subscriber. While the subscriber
- * has an open connection the LU expiry timer must remain disabled.
- * Otherwise we would kick the subscriber off the network when the timer
- * expires e.g. during a long phone call.
- * The LU expiry timer will restart once the connection is closed. */
- conn->vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION;
-
- if (!ran_conn_fsm_has_active_transactions(fi))
- osmo_fsm_inst_dispatch(fi, RAN_CONN_E_UNUSED, NULL);
-}
-
-static void ran_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- switch (event) {
- case RAN_CONN_E_COMPLETE_LAYER_3:
- /* When Authentication is off, we may already be in the Accepted state when the code
- * evaluates the Compl L3. Simply ignore. This just cosmetically mutes the error log
- * about the useless event. */
- return;
-
- case RAN_CONN_E_COMMUNICATING:
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_COMMUNICATING, 0, 0);
- return;
-
- case RAN_CONN_E_MO_CLOSE:
- case RAN_CONN_E_CN_CLOSE:
- log_close_event(fi, event, data);
- /* fall through */
- case RAN_CONN_E_UNUSED:
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
- return;
-
- default:
- OSMO_ASSERT(false);
- }
-}
-
-static void ran_conn_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- switch (event) {
- case RAN_CONN_E_COMMUNICATING:
- /* no-op */
- return;
-
- case RAN_CONN_E_MO_CLOSE:
- case RAN_CONN_E_CN_CLOSE:
- log_close_event(fi, event, data);
- /* fall through */
- case RAN_CONN_E_UNUSED:
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
- return;
-
- default:
- OSMO_ASSERT(false);
- }
-}
-
-static int ran_conn_fsm_timeout(struct osmo_fsm_inst *fi)
-{
- struct ran_conn *conn = fi->priv;
- if (ran_conn_in_release(conn)) {
- LOGPFSML(fi, LOGL_ERROR, "Timeout while releasing, discarding right now\n");
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, NULL);
- } else {
- enum gsm48_reject_value cause = GSM48_REJECT_CONGESTION;
- osmo_fsm_inst_dispatch(fi, RAN_CONN_E_CN_CLOSE, &cause);
- }
- return 0;
-}
-
-static void ran_conn_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- struct ran_conn *conn = fi->priv;
-
- /* The SGs interface needs to access vsub struct members to send the
- * release message, however the following release procedures will
- * remove conn->vsub, so we need to send the release right now. */
- if (conn->via_ran == OSMO_RAT_EUTRAN_SGS) {
- sgs_iface_tx_release(conn);
- }
-
- /* Use count for either conn->a.waiting_for_clear_complete or
- * conn->iu.waiting_for_release_complete. 'get' it early, so we don't deallocate after tearing
- * down active transactions. Safeguard against double-get (though it shouldn't happen). */
- if (!ran_conn_used_by(conn, RAN_CONN_USE_RELEASE))
- ran_conn_get(conn, RAN_CONN_USE_RELEASE);
-
- /* Cancel pending CM Service Requests */
- if (conn->received_cm_service_request) {
- conn->received_cm_service_request = false;
- ran_conn_put(conn, RAN_CONN_USE_CM_SERVICE);
- }
-
- /* Cancel all VLR FSMs, if any */
- vlr_subscr_cancel_attach_fsm(conn->vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION);
-
- if (conn->vsub) {
- /* The subscriber has no active connection anymore.
- * Restart the periodic Location Update expiry timer for this subscriber. */
- vlr_subscr_enable_expire_lu(conn->vsub);
- }
- /* If we're closing in a middle of a trans, we need to clean up */
- trans_conn_closed(conn);
-
- switch (conn->via_ran) {
- case OSMO_RAT_GERAN_A:
- a_iface_tx_clear_cmd(conn);
- if (conn->a.waiting_for_clear_complete) {
- LOGPFSML(fi, LOGL_ERROR,
- "Unexpected: conn is already waiting for BSSMAP Clear Complete\n");
- break;
- }
- conn->a.waiting_for_clear_complete = true;
- break;
- case OSMO_RAT_UTRAN_IU:
- ranap_iu_tx_release(conn->iu.ue_ctx, NULL);
- if (conn->iu.waiting_for_release_complete) {
- LOGPFSML(fi, LOGL_ERROR,
- "Unexpected: conn is already waiting for Iu Release Complete\n");
+ if (!already_used)
break;
- }
- conn->iu.waiting_for_release_complete = true;
- break;
- case OSMO_RAT_EUTRAN_SGS:
- /* Release message is already sent at the beginning of this
- * functions (see above), but we still need to notify the
- * conn that a release has been sent / is in progress. */
- ran_conn_sgs_release_sent(conn);
- break;
- default:
- LOGP(DMM, LOGL_ERROR, "%s: Unknown RAN type, cannot tx release/clear\n",
- vlr_subscr_name(conn->vsub));
- break;
}
+ if (already_used)
+ return NULL;
+ LOG_RAN_PEER(ran_peer, LOGL_DEBUG, "Outgoing conn id: %u\n", conn_id);
+ return ran_conn_create_incoming(ran_peer, conn_id);
}
-static void ran_conn_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- OSMO_ASSERT(event == RAN_CONN_E_UNUSED);
- osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASED, 0, 0);
-}
-
-static void ran_conn_fsm_released(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- /* Terminate, deallocate and also deallocate the ran_conn, which is allocated as
- * a talloc child of fi. Also calls the cleanup function. */
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
-}
-
-#define S(x) (1 << (x))
-
-static const struct osmo_fsm_state ran_conn_fsm_states[] = {
- [RAN_CONN_S_NEW] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_NEW),
- .in_event_mask = S(RAN_CONN_E_COMPLETE_LAYER_3) |
- S(RAN_CONN_E_ACCEPTED) |
- S(RAN_CONN_E_MO_CLOSE) |
- S(RAN_CONN_E_CN_CLOSE) |
- S(RAN_CONN_E_UNUSED),
- .out_state_mask = S(RAN_CONN_S_AUTH_CIPH) |
- S(RAN_CONN_S_ACCEPTED) |
- S(RAN_CONN_S_RELEASING),
- .action = ran_conn_fsm_new,
- },
- [RAN_CONN_S_AUTH_CIPH] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_AUTH_CIPH),
- .in_event_mask = S(RAN_CONN_E_ACCEPTED) |
- S(RAN_CONN_E_MO_CLOSE) |
- S(RAN_CONN_E_CN_CLOSE) |
- S(RAN_CONN_E_UNUSED),
- .out_state_mask = S(RAN_CONN_S_WAIT_CLASSMARK_UPDATE) |
- S(RAN_CONN_S_ACCEPTED) |
- S(RAN_CONN_S_RELEASING),
- .action = ran_conn_fsm_auth_ciph,
- },
- [RAN_CONN_S_WAIT_CLASSMARK_UPDATE] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_WAIT_CLASSMARK_UPDATE),
- .in_event_mask = S(RAN_CONN_E_CLASSMARK_UPDATE) |
- S(RAN_CONN_E_MO_CLOSE) |
- S(RAN_CONN_E_CN_CLOSE) |
- S(RAN_CONN_E_UNUSED),
- .out_state_mask = S(RAN_CONN_S_AUTH_CIPH) |
- S(RAN_CONN_S_RELEASING),
- .action = ran_conn_fsm_wait_classmark_update,
- },
- [RAN_CONN_S_ACCEPTED] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_ACCEPTED),
- /* allow everything to release for any odd behavior */
- .in_event_mask = S(RAN_CONN_E_COMPLETE_LAYER_3) |
- S(RAN_CONN_E_COMMUNICATING) |
- S(RAN_CONN_E_RELEASE_WHEN_UNUSED) |
- S(RAN_CONN_E_ACCEPTED) |
- S(RAN_CONN_E_MO_CLOSE) |
- S(RAN_CONN_E_CN_CLOSE) |
- S(RAN_CONN_E_UNUSED),
- .out_state_mask = S(RAN_CONN_S_RELEASING) |
- S(RAN_CONN_S_COMMUNICATING),
- .onenter = ran_conn_fsm_accepted_enter,
- .action = ran_conn_fsm_accepted,
- },
- [RAN_CONN_S_COMMUNICATING] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_COMMUNICATING),
- /* allow everything to release for any odd behavior */
- .in_event_mask = S(RAN_CONN_E_RELEASE_WHEN_UNUSED) |
- S(RAN_CONN_E_ACCEPTED) |
- S(RAN_CONN_E_COMMUNICATING) |
- S(RAN_CONN_E_MO_CLOSE) |
- S(RAN_CONN_E_CN_CLOSE) |
- S(RAN_CONN_E_UNUSED),
- .out_state_mask = S(RAN_CONN_S_RELEASING),
- .action = ran_conn_fsm_communicating,
- },
- [RAN_CONN_S_RELEASING] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_RELEASING),
- .in_event_mask = S(RAN_CONN_E_UNUSED),
- .out_state_mask = S(RAN_CONN_S_RELEASED),
- .onenter = ran_conn_fsm_releasing_onenter,
- .action = ran_conn_fsm_releasing,
- },
- [RAN_CONN_S_RELEASED] = {
- .name = OSMO_STRINGIFY(RAN_CONN_S_RELEASED),
- .onenter = ran_conn_fsm_released,
- },
-};
-
-static void ran_conn_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause);
-
-static struct osmo_fsm ran_conn_fsm = {
- .name = "RAN_conn",
- .states = ran_conn_fsm_states,
- .num_states = ARRAY_SIZE(ran_conn_fsm_states),
- .allstate_event_mask = 0,
- .allstate_action = NULL,
- .log_subsys = DMM,
- .event_names = ran_conn_fsm_event_names,
- .cleanup = ran_conn_fsm_cleanup,
- .timer_cb = ran_conn_fsm_timeout,
-};
-
/* Return statically allocated string of the ran_conn RAT type and id. */
-const char *ran_conn_get_conn_id(struct ran_conn *conn)
+const char *ran_conn_name(struct ran_conn *conn)
{
static char id[42];
int rc;
- uint32_t conn_id;
+ const char *ran_peer_name;
- switch (conn->via_ran) {
- case OSMO_RAT_GERAN_A:
- conn_id = conn->a.conn_id;
- break;
- case OSMO_RAT_UTRAN_IU:
- conn_id = iu_get_conn_id(conn->iu.ue_ctx);
- break;
- default:
- return "ran-unknown";
- }
+ if (!conn)
+ return "ran_conn==NULL";
+
+ if (!conn->ran_peer || !conn->ran_peer->sri || !conn->ran_peer->sri->ran)
+ ran_peer_name = "no-RAN-peer";
+ else
+ ran_peer_name = osmo_rat_type_name(conn->ran_peer->sri->ran->type);
- rc = snprintf(id, sizeof(id), "%s-%u", osmo_rat_type_name(conn->via_ran), conn_id);
+ rc = snprintf(id, sizeof(id), "%s-%u", ran_peer_name, conn->sccp_conn_id);
/* < 0 is error, == 0 is empty, >= size means truncation. Not really expecting this to catch on in any practical
* situation. */
- if (rc <= 0 || rc >= sizeof(id)) {
- LOGP(DMM, LOGL_ERROR, "Error with conn id; rc=%d\n", rc);
- return "conn-id-error";
- }
+ if (rc <= 0 || rc >= sizeof(id))
+ return "conn-name-error";
return id;
}
-/* Tidy up before the FSM deallocates */
-static void ran_conn_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+int ran_conn_down_l2_co(struct ran_conn *conn, struct msgb *l3, bool initial)
{
- struct ran_conn *conn = fi->priv;
-
- if (ran_conn_fsm_has_active_transactions(fi)) {
- LOGPFSML(fi, LOGL_ERROR, "Deallocating despite active transactions\n");
- trans_conn_closed(conn);
- }
-
- if (!conn) {
- LOGP(DRLL, LOGL_ERROR, "Freeing NULL RAN connection\n");
- return;
- }
-
- if (conn->vsub) {
- DEBUGP(DRLL, "%s: Freeing RAN connection\n", vlr_subscr_name(conn->vsub));
- conn->vsub->lu_fsm = NULL;
- conn->vsub->msc_conn_ref = NULL;
- vlr_subscr_put(conn->vsub, VSUB_USE_CONN);
- conn->vsub = NULL;
- } else
- DEBUGP(DRLL, "Freeing RAN connection with NULL subscriber\n");
-
- llist_del(&conn->entry);
-}
-
-/* Signal success of Complete Layer 3. Allow to keep the conn open for Auth and Ciph. */
-void ran_conn_complete_layer_3(struct ran_conn *conn)
-{
- if (!conn)
- return;
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_COMPLETE_LAYER_3, NULL);
-}
-
-void ran_conn_release_when_unused(struct ran_conn *conn)
-{
- if (!conn)
- return;
- if (ran_conn_in_release(conn)) {
- DEBUGP(DMM, "%s: %s: conn already in release (%s)\n",
- vlr_subscr_name(conn->vsub), __func__,
- osmo_fsm_inst_state_name(conn->fi));
- return;
- }
- if (conn->fi->state == RAN_CONN_S_NEW) {
- DEBUGP(DMM, "%s: %s: conn still being established (%s)\n",
- vlr_subscr_name(conn->vsub), __func__,
- osmo_fsm_inst_state_name(conn->fi));
- return;
- }
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_RELEASE_WHEN_UNUSED, NULL);
-}
-
-static void conn_close(struct ran_conn *conn, uint32_t cause, uint32_t event)
-{
- if (!conn) {
- LOGP(DMM, LOGL_ERROR, "Cannot release NULL connection\n");
- return;
- }
- if (ran_conn_in_release(conn)) {
- DEBUGP(DMM, "%s(vsub=%s, cause=%u): already in release, ignore.\n",
- __func__, vlr_subscr_name(conn->vsub), cause);
- return;
- }
- osmo_fsm_inst_dispatch(conn->fi, event, &cause);
-}
-
-void ran_conn_close(struct ran_conn *conn, uint32_t cause)
-{
- return conn_close(conn, cause, RAN_CONN_E_CN_CLOSE);
-}
-
-void ran_conn_mo_close(struct ran_conn *conn, uint32_t cause)
-{
- return conn_close(conn, cause, RAN_CONN_E_MO_CLOSE);
-}
-
-bool ran_conn_in_release(struct ran_conn *conn)
-{
- if (!conn || !conn->fi)
- return true;
- if (conn->fi->state == RAN_CONN_S_RELEASING)
- return true;
- if (conn->fi->state == RAN_CONN_S_RELEASED)
- return true;
- return false;
-}
-
-bool ran_conn_is_accepted(const struct ran_conn *conn)
-{
- if (!conn)
- return false;
- if (!conn->vsub)
- return false;
- if (!(conn->fi->state == RAN_CONN_S_ACCEPTED
- || conn->fi->state == RAN_CONN_S_COMMUNICATING))
- return false;
- return true;
-}
-
-/* Indicate that *some* communication is happening with the phone, so that the conn FSM no longer times
- * out to release within a few seconds. */
-void ran_conn_communicating(struct ran_conn *conn)
-{
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_COMMUNICATING, NULL);
-}
-
-void ran_conn_init(void)
-{
- osmo_fsm_register(&ran_conn_fsm);
-}
-
-/* Allocate a new RAN conn and FSM.
- * Deallocation is by ran_conn_put(): when the use count reaches zero, the
- * RAN_CONN_E_RELEASE_COMPLETE event is dispatched, the FSM terminates and deallocates both FSM and
- * conn. As long as the FSM is waiting for responses from the subscriber, it will itself hold a use count
- * on the conn. */
-struct ran_conn *ran_conn_alloc(struct gsm_network *network,
- enum osmo_rat_type via_ran, uint16_t lac)
-{
- struct ran_conn *conn;
- struct osmo_fsm_inst *fi;
-
- fi = osmo_fsm_inst_alloc(&ran_conn_fsm, network, NULL, LOGL_DEBUG, NULL);
- if (!fi) {
- LOGP(DMM, LOGL_ERROR, "Failed to allocate conn FSM\n");
- return NULL;
- }
-
- conn = talloc_zero(fi, struct ran_conn);
- if (!conn) {
- osmo_fsm_inst_free(fi);
- return NULL;
- }
-
- *conn = (struct ran_conn){
- .network = network,
- .via_ran = via_ran,
- .lac = lac,
- .fi = fi,
+ struct ran_peer_ev_ctx co = {
+ .conn_id = conn->sccp_conn_id,
+ .conn = conn,
+ .msg = l3,
};
-
- switch (via_ran) {
- case OSMO_RAT_GERAN_A:
- conn->log_subsys = DBSSAP;
- break;
- case OSMO_RAT_UTRAN_IU:
- conn->log_subsys = DRANAP;
- break;
- case OSMO_RAT_EUTRAN_SGS:
- conn->log_subsys = DSGS;
- break;
- default:
- conn->log_subsys = DMSC;
- break;
- }
-
- fi->priv = conn;
- llist_add_tail(&conn->entry, &network->ran_conns);
- return conn;
+ if (!conn->ran_peer)
+ return -EIO;
+ return osmo_fsm_inst_dispatch(conn->ran_peer->fi,
+ initial ? RAN_PEER_EV_MSG_DOWN_CO_INITIAL : RAN_PEER_EV_MSG_DOWN_CO,
+ &co);
}
-bool ran_conn_is_establishing_auth_ciph(const struct ran_conn *conn)
+void ran_conn_msc_role_gone(struct ran_conn *conn, struct osmo_fsm_inst *msc_role)
{
if (!conn)
- return false;
- return conn->fi->state == RAN_CONN_S_AUTH_CIPH;
-}
-
-
-const struct value_string complete_layer3_type_names[] = {
- { COMPLETE_LAYER3_NONE, "NONE" },
- { COMPLETE_LAYER3_LU, "LU" },
- { COMPLETE_LAYER3_CM_SERVICE_REQ, "CM_SERVICE_REQ" },
- { COMPLETE_LAYER3_PAGING_RESP, "PAGING_RESP" },
- { 0, NULL }
-};
-
-static void _ran_conn_update_id(struct ran_conn *conn, const char *subscr_identity)
-{
- struct vlr_subscr *vsub = conn->vsub;
-
- if (osmo_fsm_inst_update_id_f(conn->fi, "%s:%s:%s",
- subscr_identity,
- ran_conn_get_conn_id(conn),
- complete_layer3_type_name(conn->complete_layer3_type))
- != 0)
- return; /* osmo_fsm_inst_update_id_f() will log an error. */
-
- if (vsub) {
- if (vsub->lu_fsm)
- osmo_fsm_inst_update_id(vsub->lu_fsm, conn->fi->id);
- if (vsub->auth_fsm)
- osmo_fsm_inst_update_id(vsub->auth_fsm, conn->fi->id);
- if (vsub->proc_arq_fsm)
- osmo_fsm_inst_update_id(vsub->proc_arq_fsm, conn->fi->id);
- }
-
- LOGPFSML(conn->fi, LOGL_DEBUG, "Updated ID\n");
-}
-
-/* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */
-void ran_conn_update_id_from_mi(struct ran_conn *conn, const uint8_t *mi, uint8_t mi_len)
-{
- _ran_conn_update_id(conn, osmo_mi_name(mi, mi_len));
-}
-
-/* Update ran_conn->fi id string from current conn->vsub and conn->complete_layer3_type. */
-void ran_conn_update_id(struct ran_conn *conn)
-{
- _ran_conn_update_id(conn, vlr_subscr_name(conn->vsub));
-}
-
-/* Iterate all ran_conn instances that are relevant for this subscriber, and update FSM ID strings for all of the FSM
- * instances. */
-void ran_conn_update_id_for_vsub(struct vlr_subscr *for_vsub)
-{
- struct gsm_network *network;
- struct ran_conn *conn;
- if (!for_vsub)
return;
- network = for_vsub->vlr->user_ctx;
- OSMO_ASSERT(network);
+ if (conn->msc_role != msc_role)
+ return;
- llist_for_each_entry(conn, &network->ran_conns, entry) {
- if (conn->vsub == for_vsub)
- ran_conn_update_id(conn);
- }
+ conn->msc_role = NULL;
+ ran_conn_close(conn);
}
-static void rx_close_complete(struct ran_conn *conn, const char *label, bool *flag)
+/* Regularly close the conn */
+void ran_conn_close(struct ran_conn *conn)
{
if (!conn)
return;
- if (!ran_conn_in_release(conn)) {
- LOGPFSML(conn->fi, LOGL_ERROR, "Received unexpected %s, discarding right now\n",
- label);
- trans_conn_closed(conn);
- osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_ERROR, NULL);
+ if (conn->closing)
return;
- }
- if (*flag) {
- *flag = false;
- ran_conn_put(conn, RAN_CONN_USE_RELEASE);
- }
-}
+ conn->closing = true;
+ LOG_RAN_PEER(conn->ran_peer, LOGL_DEBUG, "Closing %s\n", ran_conn_name(conn));
-void ran_conn_rx_bssmap_clear_complete(struct ran_conn *conn)
-{
- rx_close_complete(conn, "BSSMAP Clear Complete", &conn->a.waiting_for_clear_complete);
-}
-
-void ran_conn_rx_iu_release_complete(struct ran_conn *conn)
-{
- rx_close_complete(conn, "Iu Release Complete", &conn->iu.waiting_for_release_complete);
-}
-
-void ran_conn_sgs_release_sent(struct ran_conn *conn)
-{
- bool dummy_waiting_for_release_complete = true;
-
- /* Note: In SGsAP there is no confirmation of a release. */
- rx_close_complete(conn, "SGs Release Complete", &dummy_waiting_for_release_complete);
-}
-
-const struct value_string ran_conn_use_names[] = {
- { RAN_CONN_USE_UNTRACKED, "UNTRACKED" },
- { RAN_CONN_USE_COMPL_L3, "compl_l3" },
- { RAN_CONN_USE_DTAP, "dtap" },
- { RAN_CONN_USE_AUTH_CIPH, "auth+ciph" },
- { RAN_CONN_USE_CM_SERVICE, "cm_service" },
- { RAN_CONN_USE_TRANS_CC, "trans_cc" },
- { RAN_CONN_USE_TRANS_SMS, "trans_sms" },
- { RAN_CONN_USE_TRANS_NC_SS, "trans_nc_ss" },
- { RAN_CONN_USE_SILENT_CALL, "silent_call" },
- { RAN_CONN_USE_RELEASE, "release" },
- { 0, NULL }
-};
-
-static const char *used_ref_counts_str(struct ran_conn *conn)
-{
- static char buf[256];
- int bit_nr;
- char *pos = buf;
- *pos = '\0';
-
- if (conn->use_tokens < 0)
- return "invalid";
-
-#define APPEND_STR(fmt, args...) do { \
- int remain = sizeof(buf) - (pos - buf) - 1; \
- int l = -1; \
- if (remain > 0) \
- l = snprintf(pos, remain, "%s" fmt, (pos == buf? "" : ","), ##args); \
- if (l < 0 || l > remain) { \
- buf[sizeof(buf) - 1] = '\0'; \
- return buf; \
- } \
- pos += l; \
- } while(0)
-
- for (bit_nr = 0; (1 << bit_nr) <= conn->use_tokens; bit_nr++) {
- if (conn->use_tokens & (1 << bit_nr)) {
- APPEND_STR("%s", get_value_string(ran_conn_use_names, bit_nr));
- }
+ if (conn->msc_role) {
+ osmo_fsm_inst_dispatch(conn->msc_role, MSC_EV_FROM_RAN_CONN_RELEASED, NULL);
+ conn->msc_role = NULL;
}
- return buf;
-#undef APPEND_STR
-}
-
-/* increment the ref-count. Needs to be called by every user */
-struct ran_conn *_ran_conn_get(struct ran_conn *conn, enum ran_conn_use balance_token,
- const char *file, int line)
-{
- OSMO_ASSERT(conn);
- if (balance_token != RAN_CONN_USE_UNTRACKED) {
- uint32_t flag = 1 << balance_token;
- OSMO_ASSERT(balance_token < 32);
- if (conn->use_tokens & flag)
- LOGPSRC(DREF, LOGL_ERROR, file, line,
- "%s: MSC conn use error: using an already used token: %s\n",
- vlr_subscr_name(conn->vsub),
- ran_conn_use_name(balance_token));
- conn->use_tokens |= flag;
+ if (conn->ran_peer) {
+ /* Todo: pass a useful SCCP cause? */
+ sccp_ran_disconnect(conn->ran_peer->sri, conn->sccp_conn_id, 0);
+ conn->ran_peer = NULL;
}
- conn->use_count++;
- LOGPSRC(DREF, LOGL_DEBUG, file, line,
- "%s: MSC conn use + %s == %u (0x%x: %s)\n",
- vlr_subscr_name(conn->vsub), ran_conn_use_name(balance_token),
- conn->use_count, conn->use_tokens, used_ref_counts_str(conn));
-
- return conn;
+ LOG_RAN_PEER(conn->ran_peer, LOGL_DEBUG, "Deallocating %s\n", ran_conn_name(conn));
+ llist_del(&conn->entry);
+ talloc_free(conn);
}
-/* decrement the ref-count. Once it reaches zero, we release */
-void _ran_conn_put(struct ran_conn *conn, enum ran_conn_use balance_token,
- const char *file, int line)
+/* Same as ran_conn_close() but without sending any SCCP messages (e.g. after RESET) */
+void ran_conn_discard(struct ran_conn *conn)
{
- OSMO_ASSERT(conn);
-
- if (balance_token != RAN_CONN_USE_UNTRACKED) {
- uint32_t flag = 1 << balance_token;
- OSMO_ASSERT(balance_token < 32);
- if (!(conn->use_tokens & flag))
- LOGPSRC(DREF, LOGL_ERROR, file, line,
- "%s: MSC conn use error: freeing an unused token: %s\n",
- vlr_subscr_name(conn->vsub),
- ran_conn_use_name(balance_token));
- conn->use_tokens &= ~flag;
- }
-
- if (conn->use_count == 0) {
- LOGPSRC(DREF, LOGL_ERROR, file, line,
- "%s: MSC conn use - %s failed: is already 0\n",
- vlr_subscr_name(conn->vsub),
- ran_conn_use_name(balance_token));
+ if (!conn)
return;
- }
-
- conn->use_count--;
- LOGPSRC(DREF, LOGL_DEBUG, file, line,
- "%s: MSC conn use - %s == %u (0x%x: %s)\n",
- vlr_subscr_name(conn->vsub), ran_conn_use_name(balance_token),
- conn->use_count, conn->use_tokens, used_ref_counts_str(conn));
-
- if (conn->use_count == 0)
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_UNUSED, NULL);
-}
-
-bool ran_conn_used_by(struct ran_conn *conn, enum ran_conn_use token)
-{
- return conn && (conn->use_tokens & (1 << token));
+ /* Make sure to drop dead and don't dispatch things like DISCONNECT requests on SCCP. */
+ conn->ran_peer = NULL;
+ ran_conn_close(conn);
}
diff --git a/src/libmsc/ran_infra.c b/src/libmsc/ran_infra.c
new file mode 100644
index 000000000..a3a7457f8
--- /dev/null
+++ b/src/libmsc/ran_infra.c
@@ -0,0 +1,118 @@
+/* Lookup table for various RAN implementations */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/tdef.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/ran_msg_a.h>
+#include <osmocom/msc/ran_msg_iu.h>
+#include <osmocom/msc/ran_peer.h>
+
+#include <osmocom/msc/ran_infra.h>
+
+#include "bscconfig.h"
+
+const struct value_string an_proto_names[] = {
+ { OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006, "Ts3G-48006" },
+ { OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413, "Ts3G-25413" },
+ {}
+};
+
+#define RAN_TDEFS \
+ { .T = -1, .default_val = 5, .desc = "RAN connection Complete Layer 3, Authentication and Ciphering timeout" }, \
+ { .T = -2, .default_val = 30, .desc = "RAN connection release sanity timeout" }, \
+ { .T = -3, .default_val = 10, .desc = "Timeout to find a target BSS after Handover Required" }, \
+
+struct osmo_tdef msc_tdefs_geran[] = {
+ RAN_TDEFS
+ {}
+};
+
+struct osmo_tdef msc_tdefs_utran[] = {
+ RAN_TDEFS
+ {}
+};
+
+struct osmo_tdef msc_tdefs_sgs[] = {
+ {}
+};
+
+static __attribute__((constructor)) void ran_infra_init()
+{
+ osmo_tdefs_reset(msc_tdefs_geran);
+ osmo_tdefs_reset(msc_tdefs_utran);
+ osmo_tdefs_reset(msc_tdefs_sgs);
+}
+
+struct ran_infra msc_ran_infra[] = {
+ [OSMO_RAT_UNKNOWN] = {
+ .type = OSMO_RAT_UNKNOWN,
+ .log_subsys = DMSC,
+ .tdefs = msc_tdefs_geran,
+ },
+ [OSMO_RAT_GERAN_A] = {
+ .type = OSMO_RAT_GERAN_A,
+ .an_proto = OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006,
+ .ssn = OSMO_SCCP_SSN_BSSAP,
+ .log_subsys = DBSSAP,
+ .tdefs = msc_tdefs_geran,
+ .sccp_ran_ops = {
+ .up_l2 = ran_peer_up_l2,
+ .disconnect = ran_peer_disconnect,
+ .is_reset_msg = bssmap_is_reset_msg,
+ .make_reset_msg = bssmap_make_reset_msg,
+ .make_paging_msg = bssmap_make_paging_msg,
+ .msg_name = bssmap_msg_name,
+ },
+ .ran_dec_l2 = ran_a_decode_l2,
+ .ran_encode = ran_a_encode,
+ },
+ [OSMO_RAT_UTRAN_IU] = {
+ .type = OSMO_RAT_UTRAN_IU,
+ .an_proto = OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413,
+ .ssn = OSMO_SCCP_SSN_RANAP,
+ .log_subsys = DIUCS,
+ .tdefs = msc_tdefs_utran,
+#if BUILD_IU
+ .sccp_ran_ops = {
+ .up_l2 = ran_peer_up_l2,
+ .disconnect = ran_peer_disconnect,
+ .is_reset_msg = ranap_is_reset_msg,
+ .make_reset_msg = ranap_make_reset_msg,
+ .make_paging_msg = ranap_make_paging_msg,
+ .msg_name = ranap_msg_name,
+ },
+ .ran_dec_l2 = ran_iu_decode_l2,
+ .ran_encode = ran_iu_encode,
+#endif
+ },
+ [OSMO_RAT_EUTRAN_SGS] = {
+ .type = OSMO_RAT_EUTRAN_SGS,
+ .log_subsys = DSGS,
+ .ran_encode = NULL,
+ .tdefs = msc_tdefs_sgs,
+ },
+};
+
+const int msc_ran_infra_len = ARRAY_SIZE(msc_ran_infra);
diff --git a/src/libmsc/ran_msg.c b/src/libmsc/ran_msg.c
new file mode 100644
index 000000000..46816a961
--- /dev/null
+++ b/src/libmsc/ran_msg.c
@@ -0,0 +1,160 @@
+/* Common bits for RAN message handling */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <osmocom/core/utils.h>
+
+#include <osmocom/msc/ran_msg.h>
+
+const struct value_string ran_msg_type_names[] = {
+ { RAN_MSG_NONE, "NONE" },
+ { RAN_MSG_COMPL_L3, "COMPL_L3" },
+ { RAN_MSG_DTAP, "DTAP" },
+ { RAN_MSG_CLEAR_COMMAND, "CLEAR_COMMAND" },
+ { RAN_MSG_CLEAR_REQUEST, "CLEAR_REQUEST" },
+ { RAN_MSG_CLEAR_COMPLETE, "CLEAR_COMPLETE" },
+ { RAN_MSG_CLASSMARK_REQUEST, "CLASSMARK_REQUEST" },
+ { RAN_MSG_CLASSMARK_UPDATE, "CLASSMARK_UPDATE" },
+ { RAN_MSG_CIPHER_MODE_COMMAND, "CIPHER_MODE_COMMAND" },
+ { RAN_MSG_CIPHER_MODE_COMPLETE, "CIPHER_MODE_COMPLETE" },
+ { RAN_MSG_CIPHER_MODE_REJECT, "CIPHER_MODE_REJECT" },
+ { RAN_MSG_COMMON_ID, "COMMON_ID" },
+ { RAN_MSG_ASSIGNMENT_COMMAND, "ASSIGNMENT_COMMAND" },
+ { RAN_MSG_ASSIGNMENT_COMPLETE, "ASSIGNMENT_COMPLETE" },
+ { RAN_MSG_ASSIGNMENT_FAILURE, "ASSIGNMENT_FAILURE" },
+ { RAN_MSG_SAPI_N_REJECT, "SAPI_N_REJECT" },
+ { RAN_MSG_LCLS_STATUS, "LCLS_STATUS" },
+ { RAN_MSG_LCLS_BREAK_REQ, "LCLS_BREAK_REQ" },
+ { RAN_MSG_HANDOVER_COMMAND, "HANDOVER_COMMAND" },
+ { RAN_MSG_HANDOVER_SUCCEEDED, "HANDOVER_SUCCEEDED" },
+ { RAN_MSG_HANDOVER_PERFORMED, "HANDOVER_PERFORMED" },
+ { RAN_MSG_HANDOVER_REQUIRED, "HANDOVER_REQUIRED" },
+ { RAN_MSG_HANDOVER_REQUIRED_REJECT, "HANDOVER_REQUIRED_REJECT" },
+ { RAN_MSG_HANDOVER_REQUEST, "HANDOVER_REQUEST" },
+ { RAN_MSG_HANDOVER_REQUEST_ACK, "HANDOVER_REQUEST_ACK" },
+ { RAN_MSG_HANDOVER_DETECT, "HANDOVER_DETECT" },
+ { RAN_MSG_HANDOVER_COMPLETE, "HANDOVER_COMPLETE" },
+ { RAN_MSG_HANDOVER_FAILURE, "HANDOVER_FAILURE" },
+ {}
+};
+
+/* extract the N(SD) and return the modulo value for a R99 message */
+static uint8_t ran_dec_dtap_undup_determine_nsd_ret_modulo_r99(uint8_t pdisc, uint8_t msg_type, uint8_t *n_sd)
+{
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ case GSM48_PDISC_CC:
+ case GSM48_PDISC_NC_SS:
+ *n_sd = (msg_type >> 6) & 0x3;
+ return 4;
+ case GSM48_PDISC_GROUP_CC:
+ case GSM48_PDISC_BCAST_CC:
+ case GSM48_PDISC_LOC:
+ *n_sd = (msg_type >> 6) & 0x1;
+ return 2;
+ default:
+ /* no sequence number, we cannot detect dups */
+ return 0;
+ }
+}
+
+/* extract the N(SD) and return the modulo value for a R98 message */
+static uint8_t gsm0407_determine_nsd_ret_modulo_r98(uint8_t pdisc, uint8_t msg_type, uint8_t *n_sd)
+{
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ case GSM48_PDISC_CC:
+ case GSM48_PDISC_NC_SS:
+ case GSM48_PDISC_GROUP_CC:
+ case GSM48_PDISC_BCAST_CC:
+ case GSM48_PDISC_LOC:
+ *n_sd = (msg_type >> 6) & 0x1;
+ return 2;
+ default:
+ /* no sequence number, we cannot detect dups */
+ return 0;
+ }
+}
+
+/* TS 24.007 11.2.3.2.3 Message Type Octet / Duplicate Detection.
+ * (Not static for unit testing). */
+int ran_dec_dtap_undup_pdisc_ctr_bin(uint8_t pdisc)
+{
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ case GSM48_PDISC_CC:
+ case GSM48_PDISC_NC_SS:
+ return 0;
+ case GSM48_PDISC_GROUP_CC:
+ return 1;
+ case GSM48_PDISC_BCAST_CC:
+ return 2;
+ case GSM48_PDISC_LOC:
+ return 3;
+ default:
+ return -1;
+ }
+}
+
+/* TS 24.007 11.2.3.2 Message Type Octet / Duplicate Detection */
+bool ran_dec_dtap_undup_is_duplicate(struct osmo_fsm_inst *log_fi, uint8_t *n_sd_next, bool is_r99, struct msgb *l3)
+{
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ uint8_t n_sd, modulo;
+ int bin;
+
+ gh = msgb_l3(l3);
+ pdisc = gsm48_hdr_pdisc(gh);
+
+ if (is_r99) {
+ modulo = ran_dec_dtap_undup_determine_nsd_ret_modulo_r99(pdisc, gh->msg_type, &n_sd);
+ } else { /* pre R99 */
+ modulo = gsm0407_determine_nsd_ret_modulo_r98(pdisc, gh->msg_type, &n_sd);
+ }
+ if (modulo == 0)
+ return false;
+ bin = ran_dec_dtap_undup_pdisc_ctr_bin(pdisc);
+ if (bin < 0)
+ return false;
+
+ OSMO_ASSERT(bin >= 0 && bin < 4);
+ if (n_sd != n_sd_next[bin]) {
+ /* not what we expected: duplicate */
+ LOGPFSML(log_fi, LOGL_NOTICE, "Duplicate DTAP: bin=%d, expected n_sd == %u, got %u\n",
+ bin, n_sd_next[bin], n_sd);
+ return true;
+ } else {
+ /* as expected: no dup; update expected counter for next message */
+ n_sd_next[bin] = (n_sd + 1) % modulo;
+ return false;
+ }
+}
+
+/* convenience: RAN decode implementations can call this to dispatch the decode_cb with a decoded ran_msg. */
+int ran_decoded(struct ran_dec *ran_dec, struct ran_msg *ran_msg)
+{
+ if (!ran_dec->decode_cb)
+ return -1;
+ return ran_dec->decode_cb(ran_dec->caller_fi, ran_dec->caller_data, ran_msg);
+}
diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c
new file mode 100644
index 000000000..2e498a28b
--- /dev/null
+++ b/src/libmsc/ran_msg_a.c
@@ -0,0 +1,1284 @@
+/* BSSAP/BSSMAP encoding and decoding for MSC */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <osmocom/core/byteswap.h>
+
+#include <osmocom/crypt/auth.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/mncc.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/ran_msg_a.h>
+#include <osmocom/msc/sccp_ran.h>
+
+#define LOG_RAN_A_DEC(RAN_DEC, level, fmt, args...) \
+ LOG_RAN_DEC(RAN_DEC, DBSSAP, level, "BSSMAP: " fmt, ## args)
+
+/* Assumes presence of struct ran_dec *ran_dec and ran_dec_msg.msg_name (set) in the local scope. */
+#define LOG_RAN_A_DEC_MSG(level, fmt, args...) \
+ LOG_RAN_DEC(ran_dec, DBSSAP, level, "%s: " fmt, ran_dec_msg.msg_name, ## args)
+
+#define LOG_RAN_A_ENC(FI, level, fmt, args...) \
+ LOG_RAN_ENC(FI, DBSSAP, level, "BSSMAP: " fmt, ## args)
+
+static int ran_a_decode_l3_compl(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct gsm0808_cell_id_list2 cil;
+ struct gsm0808_cell_id cell_id;
+ struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER);
+ struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION);
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_COMPL_L3,
+ .msg_name = "BSSMAP Complete Layer 3",
+ .compl_l3 = {
+ .cell_id = &cell_id,
+ .msg = msg,
+ },
+ };
+ int rc;
+
+ if (!ie_cell_id) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory CELL IDENTIFIER not present, discarding message\n");
+ return -EINVAL;
+ }
+ if (!ie_l3_info) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory LAYER 3 INFORMATION not present, discarding message\n");
+ return -EINVAL;
+ }
+
+ /* Parse Cell ID element -- this should yield a cell identifier "list" with 1 element. */
+
+ rc = gsm0808_dec_cell_id_list2(&cil, ie_cell_id->val, ie_cell_id->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding CELL IDENTIFIER gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ if (cil.id_list_len != 1) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Unable to parse element CELL IDENTIFIER, discarding message\n");
+ return -EINVAL;
+ }
+
+ /* Sanity check the Cell Identity */
+ switch (cil.id_discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ case CELL_IDENT_LAI_AND_LAC:
+ case CELL_IDENT_LAC_AND_CI:
+ case CELL_IDENT_LAC:
+ break;
+
+ case CELL_IDENT_CI:
+ case CELL_IDENT_NO_CELL:
+ case CELL_IDENT_BSS:
+ default:
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "CELL IDENTIFIER does not specify a LAC, discarding message: %s\n",
+ gsm0808_cell_id_list_name(&cil));
+ return -EINVAL;
+ }
+
+ cell_id = (struct gsm0808_cell_id){
+ .id_discr = cil.id_discr,
+ .id = cil.id_list[0],
+ };
+
+ /* Parse Layer 3 Information element */
+ msg->l3h = (uint8_t*)ie_l3_info->val;
+ msgb_l3trim(msg, ie_l3_info->len);
+
+ if (msgb_l3len(msg) < sizeof(struct gsm48_hdr)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "too short L3 info (%d), discarding message\n", msgb_l3len(msg));
+ return -ENODATA;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_clear_request(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CLEAR_REQUEST,
+ .msg_name = "BSSMAP Clear Request",
+ };
+
+ if (!ie_cause) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Cause code is missing, using GSM0808_CAUSE_EQUIPMENT_FAILURE\n");
+ ran_dec_msg.clear_request.bssap_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE;
+ } else {
+ ran_dec_msg.clear_request.bssap_cause = ie_cause->val[0];
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_clear_complete(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CLEAR_COMPLETE,
+ .msg_name = "BSSMAP Clear Complete",
+ };
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_classmark_update(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct tlv_p_entry *ie_cm2 = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_T2);
+ struct tlv_p_entry *ie_cm3 = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_T3);
+ struct osmo_gsm48_classmark cm = {};
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CLASSMARK_UPDATE,
+ .msg_name = "BSSMAP Classmark Update",
+ .classmark_update = {
+ .classmark = &cm,
+ },
+ };
+
+ if (!ie_cm2) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "mandatory Classmark Information Type 2 not present, discarding message\n");
+ return -EINVAL;
+ }
+
+ cm.classmark2_len = OSMO_MIN(sizeof(cm.classmark2), ie_cm2->len);
+ memcpy(&cm.classmark2, ie_cm2->val, cm.classmark2_len);
+
+ if (ie_cm3) {
+ cm.classmark3_len = OSMO_MIN(sizeof(cm.classmark3), ie_cm3->len);
+ memcpy(&cm.classmark3, ie_cm3->val, cm.classmark3_len);
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_cipher_mode_complete(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct tlv_p_entry *ie_chosen_encr_alg = TLVP_GET(tp, GSM0808_IE_CHOSEN_ENCR_ALG);
+ struct tlv_p_entry *ie_l3_msg = TLVP_GET(tp, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS);
+ int rc;
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CIPHER_MODE_COMPLETE,
+ .msg_name = "BSSMAP Ciphering Mode Complete",
+ };
+
+ if (ie_chosen_encr_alg) {
+ uint8_t ie_val = ie_chosen_encr_alg->val[0];
+ /* 3GPP TS 48.008 3.2.2.44 Chosen Encryption Algorithm encodes as 1 = no encryption, 2 = A5/1, 4 = A5/3.
+ * Internally we handle without this weird off-by-one. */
+ if (ie_val < 1 || ie_val > 8)
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Unsupported value for 3.2.2.44 Chosen Encryption Algorithm: %u\n",
+ ie_val);
+ else
+ ran_dec_msg.cipher_mode_complete.alg_id = ie_chosen_encr_alg->val[0];
+ }
+
+ rc = ran_decoded(ran_dec, &ran_dec_msg);
+
+ if (ie_l3_msg) {
+ msg->l3h = (uint8_t*)ie_l3_msg->val;
+ msgb_l3trim(msg, ie_l3_msg->len);
+ ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_DTAP,
+ .msg_name = "BSSMAP Ciphering Mode Complete (L3 Message Contents)",
+ .dtap = msg,
+ };
+ ran_decoded(ran_dec, &ran_dec_msg);
+ }
+
+ return rc;
+}
+
+static int ran_a_decode_cipher_mode_reject(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ int rc;
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CIPHER_MODE_REJECT,
+ .msg_name = "BSSMAP Ciphering Mode Reject",
+ };
+
+ rc = gsm0808_get_cipher_reject_cause(tp);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "failed to extract Cause\n");
+ ran_dec_msg.cipher_mode_reject.bssap_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE;
+ } else {
+ ran_dec_msg.cipher_mode_reject.bssap_cause = (enum gsm0808_cause)rc;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+enum mgcp_codecs ran_a_mgcp_codec_from_sc(const struct gsm0808_speech_codec *sc)
+{
+ switch (sc->type) {
+ case GSM0808_SCT_FR1:
+ return CODEC_GSM_8000_1;
+ break;
+ case GSM0808_SCT_FR2:
+ return CODEC_GSMEFR_8000_1;
+ break;
+ case GSM0808_SCT_FR3:
+ return CODEC_AMR_8000_1;
+ break;
+ case GSM0808_SCT_FR4:
+ return CODEC_AMRWB_16000_1;
+ break;
+ case GSM0808_SCT_FR5:
+ return CODEC_AMRWB_16000_1;
+ break;
+ case GSM0808_SCT_HR1:
+ return CODEC_GSMHR_8000_1;
+ break;
+ case GSM0808_SCT_HR3:
+ return CODEC_AMR_8000_1;
+ break;
+ case GSM0808_SCT_HR4:
+ return CODEC_AMRWB_16000_1;
+ break;
+ case GSM0808_SCT_HR6:
+ return CODEC_AMRWB_16000_1;
+ break;
+ default:
+ return CODEC_PCMU_8000_1;
+ break;
+ }
+}
+
+static int ran_a_decode_assignment_complete(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct tlv_p_entry *ie_aoip_transp_addr = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR);
+ struct tlv_p_entry *ie_speech_codec = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC);
+ struct sockaddr_storage rtp_addr;
+ struct sockaddr_in *rtp_addr_in;
+ struct gsm0808_speech_codec sc;
+ int rc;
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_ASSIGNMENT_COMPLETE,
+ .msg_name = "BSSMAP Assignment Complete",
+ };
+
+ if (ie_aoip_transp_addr) {
+ /* Decode AoIP transport address element */
+ rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Unable to decode AoIP Transport Layer Address\n");
+ return -EINVAL;
+ }
+
+ rtp_addr_in = (struct sockaddr_in*)&rtp_addr;
+
+ if (rtp_addr.ss_family != AF_INET) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Assignment Complete: IE AoIP Transport Address:"
+ " unsupported addressing scheme (only IPV4 supported)\n");
+ return -EINVAL;
+ }
+
+ if (osmo_sockaddr_str_from_sockaddr_in(&ran_dec_msg.assignment_complete.remote_rtp, rtp_addr_in)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Assignment Complete: unable to decode remote RTP IP address\n");
+ return -EINVAL;
+ }
+ }
+
+ if (ie_speech_codec) {
+ /* Decode Speech Codec (Chosen) element */
+ rc = gsm0808_dec_speech_codec(&sc, ie_speech_codec->val, ie_speech_codec->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Assignment Complete: unable to decode IE Speech Codec (Chosen)"
+ " (rc=%d).\n", rc);
+ return -EINVAL;
+ }
+ ran_dec_msg.assignment_complete.codec_present = true;
+ ran_dec_msg.assignment_complete.codec = ran_a_mgcp_codec_from_sc(&sc);
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_assignment_failure(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ struct tlv_p_entry *ie_rr_cause = TLVP_GET(tp, GSM0808_IE_RR_CAUSE);
+ struct tlv_p_entry *ie_speech_codec_list = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST);
+ struct gsm0808_speech_codec_list scl;
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_ASSIGNMENT_FAILURE,
+ .msg_name = "BSSMAP Assignment Failure",
+ .assignment_failure = {
+ .bssap_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ .rr_cause = GSM48_RR_CAUSE_ABNORMAL_UNSPEC,
+ },
+ };
+
+ if (ie_cause)
+ ran_dec_msg.assignment_failure.bssap_cause = ie_cause->val[0];
+ if (ie_rr_cause)
+ ran_dec_msg.assignment_failure.rr_cause = ie_rr_cause->val[0];
+
+ if (ie_speech_codec_list
+ && gsm0808_dec_speech_codec_list(&scl, ie_speech_codec_list->val, ie_speech_codec_list->len) == 0)
+ ran_dec_msg.assignment_failure.scl_bss_supported = &scl;
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_sapi_n_reject(struct ran_dec *ran_dec, struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ struct tlv_p_entry *ie_dlci = TLVP_GET(tp, GSM0808_IE_DLCI);
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_SAPI_N_REJECT,
+ .msg_name = "BSSMAP SAPI-N Reject",
+ };
+
+ /* 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 */
+ if (!ie_cause) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "SAPI-N Reject: cause code IE is missing, discarding message\n");
+ return -EINVAL;
+ }
+ ran_dec_msg.sapi_n_reject.bssap_cause = ie_cause->val[0];
+
+ if (!ie_dlci) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "SAPI-N Reject: DLCI IE is missing, discarding message\n");
+ return -EINVAL;
+ }
+ ran_dec_msg.sapi_n_reject.dlci = ie_dlci->val[0];
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_lcls_notification(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ const struct tlv_p_entry *ie_lcls_bss_status = TLVP_GET(tp, GSM0808_IE_LCLS_BSS_STATUS);
+ const struct tlv_p_entry *ie_lcls_break_req = TLVP_GET(tp, GSM0808_IE_LCLS_BREAK_REQ);
+ struct ran_msg ran_dec_msg;
+
+ /* Either §3.2.2.119 LCLS-BSS-Status or §3.2.2.120 LCLS-Break-Request shall be present */
+ if ((!ie_lcls_bss_status && !ie_lcls_break_req)
+ || (ie_lcls_bss_status && ie_lcls_break_req)) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "Ignoring broken LCLS Notification message\n");
+ return -EINVAL;
+ }
+
+ if (ie_lcls_bss_status) {
+ ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_LCLS_STATUS,
+ .msg_name = "BSSMAP LCLS Notification (LCLS Status)",
+ .lcls_status = {
+ .status = ie_lcls_bss_status->len ?
+ ie_lcls_bss_status->val[0] : GSM0808_LCLS_STS_NA,
+ },
+ };
+ return ran_decoded(ran_dec, &ran_dec_msg);
+ }
+
+ if (ie_lcls_break_req) {
+ ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_LCLS_BREAK_REQ,
+ .msg_name = "BSSMAP LCLS Notification (LCLS Break Req)",
+ .lcls_break_req = {
+ .todo = 23,
+ },
+ };
+ return ran_decoded(ran_dec, &ran_dec_msg);
+ }
+
+ return -EINVAL;
+}
+
+static int ran_a_decode_handover_required(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ const struct tlv_p_entry *ie_cil = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUIRED,
+ .msg_name = "BSSMAP Handover Required",
+ };
+ /* On decoding failures, dispatch an invalid RAN_MSG_HANDOVER_REQUIRED so msc_a can pass down a
+ * BSS_MAP_MSG_HANDOVER_REQUIRED_REJECT message. */
+
+ if (ie_cause)
+ ran_dec_msg.handover_required.cause = ie_cause->val[0];
+ else
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Cause IE missing\n");
+
+ if (!ie_cil
+ || gsm0808_dec_cell_id_list2(&ran_dec_msg.handover_required.cil, ie_cil->val, ie_cil->len) <= 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "No or invalid Cell Identifier List IE\n");
+ ran_dec_msg.handover_required.cil = (struct gsm0808_cell_id_list2){};
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static uint8_t a5_encryption_mask_from_gsm0808_chosen_enc_alg(enum gsm0808_chosen_enc_alg val)
+{
+ return 1 << val;
+}
+
+static int ran_a_decode_handover_request(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct osmo_gsm48_classmark classmark = {};
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUEST,
+ .msg_name = "BSSMAP Handover Request",
+ .handover_request = {
+ .classmark = &classmark,
+ },
+ };
+ struct ran_handover_request *r = &ran_dec_msg.handover_request;
+
+ const struct tlv_p_entry *ie_channel_type = TLVP_GET(tp, GSM0808_IE_CHANNEL_TYPE);
+ const struct tlv_p_entry *ie_encryption_information = TLVP_GET(tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+ const struct tlv_p_entry *ie_classmark1 = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1);
+ const struct tlv_p_entry *ie_classmark2 = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_T2);
+ const struct tlv_p_entry *ie_cell_id_serving = TLVP_GET(&tp[0], GSM0808_IE_CELL_IDENTIFIER);
+ const struct tlv_p_entry *ie_cell_id_target = TLVP_GET(&tp[1], GSM0808_IE_CELL_IDENTIFIER);
+ const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ const struct tlv_p_entry *ie_classmark3 = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_T3);
+ const struct tlv_p_entry *ie_current_channel_type_1 = TLVP_GET(tp, GSM0808_IE_CURRENT_CHANNEL_TYPE_1);
+ const struct tlv_p_entry *ie_speech_version_used = TLVP_GET(tp, GSM0808_IE_SPEECH_VERSION);
+ const struct tlv_p_entry *ie_chosen_encr_alg_serving = TLVP_GET(tp, GSM0808_IE_CHOSEN_ENCR_ALG);
+ const struct tlv_p_entry *ie_old_bss_to_new_bss_info = TLVP_GET(tp, GSM0808_IE_OLD_BSS_TO_NEW_BSS_INFORMATION);
+ const struct tlv_p_entry *ie_imsi = TLVP_GET(tp, GSM0808_IE_IMSI);
+ const struct tlv_p_entry *ie_aoip_transp_addr = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR);
+ const struct tlv_p_entry *ie_codec_list_msc_preferred = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST);
+ const struct tlv_p_entry *ie_call_id = TLVP_GET(tp, GSM0808_IE_CALL_ID);
+ const struct tlv_p_entry *ie_global_call_ref = TLVP_GET(tp, GSM0808_IE_GLOBAL_CALL_REF);
+
+ struct gsm0808_channel_type channel_type;
+ struct gsm0808_encrypt_info encr_info;
+ struct gsm0808_speech_codec_list scl;
+ struct geran_encr geran_encr = {};
+ char imsi[OSMO_IMSI_BUF_SIZE];
+ struct osmo_sockaddr_str rtp_ran_local;
+
+ if (!ie_channel_type) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Channel Type\n");
+ return -EINVAL;
+ }
+ if (gsm0808_dec_channel_type(&channel_type, ie_channel_type->val, ie_channel_type->len) <= 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Channel Type IE\n");
+ return -EINVAL;
+ }
+ r->geran.channel_type = &channel_type;
+
+ if (ie_encryption_information) {
+ int i;
+ if (gsm0808_dec_encrypt_info(&encr_info, ie_encryption_information->val, ie_encryption_information->len)
+ <= 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Encryption Informaiton IE\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < encr_info.perm_algo_len; i++) {
+ r->geran.a5_encryption_mask |=
+ a5_encryption_mask_from_gsm0808_chosen_enc_alg(encr_info.perm_algo[i]);
+ }
+
+ if (encr_info.key_len > sizeof(geran_encr.key)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Encryption Informaiton IE:"
+ " encryption key is too long: %u\n", geran_encr.key_len);
+ return -EINVAL;
+ }
+
+ if (encr_info.key_len) {
+ memcpy(geran_encr.key, encr_info.key, encr_info.key_len);
+ geran_encr.key_len = encr_info.key_len;
+ }
+
+ r->geran.chosen_encryption = &geran_encr;
+ }
+
+ if (!ie_classmark1 && !ie_classmark2) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: either Classmark Information 1"
+ " or Classmark Information 2 must be included\n");
+ return -EINVAL;
+ }
+
+ if (ie_classmark1) {
+ if (ie_classmark1->len != sizeof(classmark.classmark1)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Invalid size for Classmark 1: %u, expected %zu\n",
+ ie_classmark1->len, sizeof(classmark.classmark1));
+ return -EINVAL;
+ }
+ memcpy((uint8_t*)&classmark.classmark1, ie_classmark1->val, ie_classmark1->len);
+ classmark.classmark1_set = true;
+ }
+
+ if (ie_classmark2) {
+ uint8_t len = OSMO_MIN(ie_classmark2->len, sizeof(classmark.classmark2));
+ memcpy((uint8_t*)&classmark.classmark2, ie_classmark2->val, len);
+ classmark.classmark2_len = len;
+ }
+
+ if (!ie_cell_id_serving) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier (Serving)\n");
+ return -EINVAL;
+ }
+ if (gsm0808_dec_cell_id(&r->cell_id_serving, ie_cell_id_serving->val,
+ ie_cell_id_serving->len) <= 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Cell Identifier (Serving) IE\n");
+ return -EINVAL;
+ }
+
+ if (!ie_cell_id_target) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier (Target)\n");
+ return -EINVAL;
+ }
+ if (gsm0808_dec_cell_id(&r->cell_id_target, ie_cell_id_target->val,
+ ie_cell_id_target->len) <= 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Cell Identifier (Target) IE\n");
+ return -EINVAL;
+ }
+
+ if (ie_cause)
+ r->bssap_cause = ie_cause->val[0];
+
+ if (ie_classmark3) {
+ uint8_t len = OSMO_MIN(ie_classmark3->len, sizeof(classmark.classmark3));
+ memcpy(classmark.classmark3, ie_classmark3->val, len);
+ classmark.classmark3_len = len;
+ }
+
+ if (ie_current_channel_type_1) {
+ r->current_channel_type_1 = ie_current_channel_type_1->val[0];
+ r->current_channel_type_1_present = true;
+ }
+
+ if (ie_speech_version_used) {
+ r->speech_version_used = ie_speech_version_used->val[0];
+ }
+
+ if (ie_chosen_encr_alg_serving && ie_chosen_encr_alg_serving->len) {
+ geran_encr.alg_id = ie_chosen_encr_alg_serving->val[0];
+ r->geran.chosen_encryption = &geran_encr;
+ }
+
+ if (ie_old_bss_to_new_bss_info) {
+ r->old_bss_to_new_bss_info_raw = ie_old_bss_to_new_bss_info->val;
+ r->old_bss_to_new_bss_info_raw_len = ie_old_bss_to_new_bss_info->len;
+ }
+
+ if (ie_imsi) {
+ gsm48_mi_to_string(imsi, sizeof(imsi), ie_imsi->val, ie_imsi->len);
+ r->imsi = imsi;
+ }
+
+ if (ie_aoip_transp_addr) {
+ do {
+ struct sockaddr_storage rtp_addr;
+ if (gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len) < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n");
+ break;
+ }
+ if (rtp_addr.ss_family != AF_INET) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "IE AoIP Transport Address:"
+ " unsupported addressing scheme (only IPV4 supported)\n");
+ break;
+ }
+ if (osmo_sockaddr_str_from_sockaddr_in(&rtp_ran_local, (struct sockaddr_in*)&rtp_addr)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode remote RTP IP address\n");
+ break;
+ }
+ r->rtp_ran_local = &rtp_ran_local;
+ } while(0);
+ }
+
+ if (ie_codec_list_msc_preferred
+ && gsm0808_dec_speech_codec_list(&scl, ie_codec_list_msc_preferred->val,
+ ie_codec_list_msc_preferred->len) == 0)
+ r->codec_list_msc_preferred = &scl;
+
+ if (ie_call_id && ie_call_id->len == 4) {
+ r->call_id = osmo_load32le(ie_call_id->val);
+ r->call_id_present = true;
+ }
+
+ if (ie_global_call_ref) {
+ r->global_call_reference = ie_global_call_ref->val;
+ r->global_call_reference_len = ie_global_call_ref->len;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_handover_request_ack(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUEST_ACK,
+ .msg_name = "BSSMAP Handover Request Acknowledge",
+ };
+ const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION);
+ const struct tlv_p_entry *ie_aoip_transp_addr = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR);
+ const struct tlv_p_entry *ie_speech_codec = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC);
+ const struct tlv_p_entry *ie_chosen_channel = TLVP_GET(tp, GSM0808_IE_CHOSEN_CHANNEL);
+ const struct tlv_p_entry *ie_chosen_encr_alg = TLVP_GET(tp, GSM0808_IE_CHOSEN_ENCR_ALG);
+ const struct tlv_p_entry *ie_chosen_speech_version = TLVP_GET(tp, GSM0808_IE_SPEECH_VERSION);
+
+ /* On missing mandatory IEs, dispatch an invalid RAN_MSG_HANDOVER_REQUEST_ACK so msc_a can act on the failure. */
+
+ if (ie_l3_info) {
+ ran_dec_msg.handover_request_ack.rr_ho_command = ie_l3_info->val;
+ ran_dec_msg.handover_request_ack.rr_ho_command_len = ie_l3_info->len;
+ }
+
+ if (ie_chosen_channel) {
+ ran_dec_msg.handover_request_ack.chosen_channel_present = true;
+ ran_dec_msg.handover_request_ack.chosen_channel = *ie_chosen_channel->val;
+ }
+
+ if (ie_chosen_encr_alg) {
+ ran_dec_msg.handover_request_ack.chosen_encr_alg = *ie_chosen_encr_alg->val;
+ if (ran_dec_msg.handover_request_ack.chosen_encr_alg < 1
+ || ran_dec_msg.handover_request_ack.chosen_encr_alg > 8) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "invalid Chosen Encryption Algorithm: %u\n",
+ ran_dec_msg.handover_request_ack.chosen_encr_alg);
+ }
+ }
+
+ if (ie_chosen_speech_version) {
+ struct gsm0808_speech_codec sc;
+ ran_dec_msg.handover_request_ack.chosen_speech_version = ie_chosen_speech_version->val[0];
+
+ /* the codec may be extrapolated from this Speech Version or below from Speech Codec */
+ gsm0808_speech_codec_from_chan_type(&sc, ran_dec_msg.handover_request_ack.chosen_speech_version);
+ ran_dec_msg.handover_request_ack.codec_present = true;
+ ran_dec_msg.handover_request_ack.codec = ran_a_mgcp_codec_from_sc(&sc);
+ }
+
+ if (ie_aoip_transp_addr) {
+ do {
+ struct sockaddr_storage rtp_addr;
+ if (gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len) < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n");
+ break;
+ }
+ if (rtp_addr.ss_family != AF_INET) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "IE AoIP Transport Address:"
+ " unsupported addressing scheme (only IPV4 supported)\n");
+ break;
+ }
+ if (osmo_sockaddr_str_from_sockaddr_in(&ran_dec_msg.handover_request_ack.remote_rtp,
+ (struct sockaddr_in*)&rtp_addr)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode remote RTP IP address\n");
+ ran_dec_msg.handover_request_ack.remote_rtp = (struct osmo_sockaddr_str){};
+ break;
+ }
+ } while(0);
+ }
+
+ if (ie_speech_codec) {
+ struct gsm0808_speech_codec sc;
+ if (gsm0808_dec_speech_codec(&sc, ie_speech_codec->val, ie_speech_codec->len) < 0)
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode IE Speech Codec (Chosen)\n");
+ else {
+ /* the codec may be extrapolated from above Speech Version or from this Speech Codec */
+ ran_dec_msg.handover_request_ack.codec_present = true;
+ ran_dec_msg.handover_request_ack.codec = ran_a_mgcp_codec_from_sc(&sc);
+ }
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_handover_detect(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_DETECT,
+ .msg_name = "BSSMAP Handover Detect",
+ };
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_handover_succeeded(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_SUCCEEDED,
+ .msg_name = "BSSMAP Handover Succeeded",
+ };
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_handover_complete(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_COMPLETE,
+ .msg_name = "BSSMAP Handover Complete",
+ };
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_handover_failure(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_HANDOVER_FAILURE,
+ .msg_name = "BSSMAP Handover Failure",
+ };
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_bssmap(struct ran_dec *ran_dec, struct msgb *bssmap)
+{
+ struct tlv_parsed tp[2];
+ int rc;
+ struct bssmap_header *h = msgb_l2(bssmap);
+ uint8_t msg_type;
+ bssmap->l3h = bssmap->l2h + sizeof(*h);
+
+ if (msgb_l3len(bssmap) < 1) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "No data received, discarding message\n");
+ return -1;
+ }
+
+ if (msgb_l3len(bssmap) < h->length) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "BSSMAP data truncated, discarding message\n");
+ return -1;
+ }
+
+ if (msgb_l3len(bssmap) > h->length) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_NOTICE, "There are %u extra bytes after the BSSMAP data, truncating\n",
+ msgb_l3len(bssmap) - h->length);
+ msgb_l3trim(bssmap, h->length);
+ }
+
+ /* h->type == BSSAP_MSG_BSS_MANAGEMENT; h->length is the data length,
+ * which starts with the MAP msg_type, followed by IEs. */
+ msg_type = bssmap->l3h[0];
+ rc = osmo_bssap_tlv_parse2(tp, ARRAY_SIZE(tp), bssmap->l3h + 1, h->length - 1);
+ if (rc < 0) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "Failed parsing TLV, discarding message\n");
+ return -EINVAL;
+ }
+
+ LOG_RAN_A_DEC(ran_dec, LOGL_DEBUG, "Rx BSSMAP DT1 %s\n", gsm0808_bssmap_name(msg_type));
+
+ switch (msg_type) {
+ case BSS_MAP_MSG_COMPLETE_LAYER_3:
+ return ran_a_decode_l3_compl(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_CLEAR_RQST:
+ return ran_a_decode_clear_request(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_CLEAR_COMPLETE:
+ return ran_a_decode_clear_complete(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_CLASSMARK_UPDATE:
+ return ran_a_decode_classmark_update(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_CIPHER_MODE_COMPLETE:
+ return ran_a_decode_cipher_mode_complete(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_CIPHER_MODE_REJECT:
+ return ran_a_decode_cipher_mode_reject(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_ASSIGMENT_COMPLETE:
+ rc = ran_a_decode_assignment_complete(ran_dec, bssmap, tp);
+ if (rc < 0) {
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_ASSIGNMENT_FAILURE,
+ .msg_name = "BSSMAP Assignment Complete but failed to decode",
+ .clear_request = {
+ .bssap_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ },
+ };
+ ran_decoded(ran_dec, &ran_dec_msg);
+ }
+ return rc;
+ case BSS_MAP_MSG_ASSIGMENT_FAILURE:
+ return ran_a_decode_assignment_failure(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_SAPI_N_REJECT:
+ return ran_a_decode_sapi_n_reject(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_LCLS_NOTIFICATION:
+ return ran_a_decode_lcls_notification(ran_dec, bssmap, tp);
+
+ /* From current RAN peer, the Handover origin: */
+ case BSS_MAP_MSG_HANDOVER_REQUIRED:
+ return ran_a_decode_handover_required(ran_dec, bssmap, tp);
+
+ /* From current MSC to remote handover target MSC */
+ case BSS_MAP_MSG_HANDOVER_RQST:
+ return ran_a_decode_handover_request(ran_dec, bssmap, tp);
+
+ /* From potential new RAN peer, the Handover target: */
+ case BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE:
+ return ran_a_decode_handover_request_ack(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_HANDOVER_DETECT:
+ return ran_a_decode_handover_detect(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_HANDOVER_SUCCEEDED:
+ return ran_a_decode_handover_succeeded(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_HANDOVER_COMPLETE:
+ return ran_a_decode_handover_complete(ran_dec, bssmap, tp);
+
+ /* From any Handover peer: */
+ case BSS_MAP_MSG_HANDOVER_FAILURE:
+ return ran_a_decode_handover_failure(ran_dec, bssmap, tp);
+
+ default:
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "Unimplemented msg type: %s\n", gsm0808_bssmap_name(msg_type));
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int ran_a_decode_l3(struct ran_dec *ran_dec, struct msgb *l3)
+{
+ struct dtap_header *dtap = msgb_l2(l3);
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_DTAP,
+ .msg_name = "BSSAP DTAP",
+ .dtap = l3,
+ };
+ l3->l3h = l3->l2h + sizeof(struct dtap_header);
+ OMSC_LINKID_CB(l3) = dtap->link_id;
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+int ran_a_decode_l2(struct ran_dec *ran_dec, struct msgb *bssap)
+{
+ uint8_t bssap_type;
+ OSMO_ASSERT(bssap);
+
+ if (!msgb_l2(bssap) || !msgb_l2len(bssap)) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "Cannot decode L2, msg->l2h is unset / empty: %s\n",
+ msgb_hexdump(bssap));
+ return -EINVAL;
+ }
+
+ if (msgb_l2len(bssap) < sizeof(struct bssmap_header)) {
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "The header is too short -- discarding message\n");
+ return -EINVAL;
+ }
+
+ bssap_type = bssap->l2h[0];
+ switch (bssap_type) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ return ran_a_decode_bssmap(ran_dec, bssap);
+ case BSSAP_MSG_DTAP:
+ return ran_a_decode_l3(ran_dec, bssap);
+ default:
+ LOG_RAN_A_DEC(ran_dec, LOGL_ERROR, "Unimplemented BSSAP msg type: %s\n", gsm0808_bssap_name(bssap_type));
+ return -EINVAL;
+ }
+}
+
+static struct msgb *ran_a_wrap_dtap(struct msgb *dtap)
+{
+ struct msgb *an_apdu;
+ dtap->l3h = dtap->data;
+ an_apdu = gsm0808_create_dtap(dtap, OMSC_LINKID_CB(dtap));
+ an_apdu->l2h = an_apdu->data;
+ msgb_free(dtap);
+ return an_apdu;
+}
+
+static int ran_a_channel_type_to_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;
+}
+
+/* Compose a BSSAP Assignment Command.
+ * Passing an RTP address is optional.
+ * The msub is passed merely for error logging. */
+static struct msgb *ran_a_make_assignment_command(struct osmo_fsm_inst *log_fi,
+ const struct ran_assignment_command *ac)
+{
+ struct gsm0808_speech_codec_list scl;
+ struct gsm0808_speech_codec_list *use_scl = NULL;
+ struct sockaddr_storage rtp_addr;
+ struct sockaddr_storage *use_rtp_addr = NULL;
+ int rc;
+
+ if (!ac->channel_type) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: missing Channel Type\n");
+ return NULL;
+ }
+
+ if (ac->channel_type->ch_indctr == GSM0808_CHAN_SPEECH) {
+ rc = ran_a_channel_type_to_speech_codec_list(&scl, ac->channel_type);
+ if (rc < 0) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: Cannot translate Channel Type to Speech Codec List\n");
+ return NULL;
+ }
+ use_scl = &scl;
+
+ /* Package RTP-Address data */
+ if (osmo_sockaddr_str_is_set(ac->cn_rtp)) {
+ struct sockaddr_in rtp_addr_in;
+
+ memset(&rtp_addr_in, 0, sizeof(rtp_addr_in));
+ rtp_addr_in.sin_family = AF_INET;
+ rtp_addr_in.sin_port = osmo_htons(ac->cn_rtp->port),
+ rtp_addr_in.sin_addr.s_addr = inet_addr(ac->cn_rtp->ip);
+
+ if (rtp_addr_in.sin_addr.s_addr == INADDR_NONE) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: Invalid RTP-Address\n");
+ return NULL;
+ }
+ if (rtp_addr_in.sin_port == 0) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: Invalid RTP-Port\n");
+ return NULL;
+ }
+
+ memset(&rtp_addr, 0, sizeof(rtp_addr));
+ memcpy(&rtp_addr, &rtp_addr_in, sizeof(rtp_addr_in));
+
+ use_rtp_addr = &rtp_addr;
+ }
+ }
+
+ return gsm0808_create_ass(ac->channel_type, NULL, use_rtp_addr, use_scl, NULL);
+}
+
+/* For an A5/N number a5_n set dst to the matching GSM0808_ALG_ID_A5_<n>. */
+static int a5_n_to_gsm0808_chosen_enc_alg(uint8_t *dst, int a5_n)
+{
+ switch (a5_n) {
+ case 0:
+ *dst = GSM0808_ALG_ID_A5_0;
+ return 0;
+ case 1:
+ *dst = GSM0808_ALG_ID_A5_1;
+ return 0;
+ case 2:
+ *dst = GSM0808_ALG_ID_A5_2;
+ return 0;
+ case 3:
+ *dst = GSM0808_ALG_ID_A5_3;
+ return 0;
+ default:
+ return -ENOTSUP;
+ }
+}
+
+static int make_encrypt_info_perm_algo(struct osmo_fsm_inst *fi, struct gsm0808_encrypt_info *ei,
+ uint8_t a5_encryption_mask, const struct osmo_gsm48_classmark *cm)
+{
+ int i;
+ int j = 0;
+ for (i = 0; i < 8; i++) {
+ int supported;
+
+ /* A5/n permitted by osmo-msc.cfg? */
+ if (!(a5_encryption_mask & (1 << i)))
+ continue;
+
+ /* A5/n supported by MS? */
+ supported = osmo_gsm48_classmark_supports_a5(cm, i);
+ if (supported != 1)
+ continue;
+
+ if (a5_n_to_gsm0808_chosen_enc_alg(&ei->perm_algo[j], i)) {
+ LOG_RAN_A_ENC(fi, LOGL_ERROR, "Not supported: A5/%d algorithm\n", i);
+ return -1;
+ }
+ j++;
+ ei->perm_algo_len = j;
+ }
+ return 0;
+}
+
+/* For ran_a_make_cipher_mode_command(), for
+ * memcpy(ei.key, cm->vec->kc, sizeof(cm->vec->kc));
+ */
+osmo_static_assert(sizeof(((struct gsm0808_encrypt_info*)0)->key) >= sizeof(((struct osmo_auth_vector*)0)->kc),
+ gsm0808_encrypt_info_key_fits_osmo_auth_vec_kc);
+static struct msgb *ran_a_make_cipher_mode_command(struct osmo_fsm_inst *fi, const struct ran_cipher_mode_command *cm)
+{
+ struct gsm0808_encrypt_info ei = {};
+ char buf[16 * 2 + 1];
+ const uint8_t cipher_response_mode = 1;
+
+ if (make_encrypt_info_perm_algo(fi, &ei, cm->geran.a5_encryption_mask, cm->classmark))
+ return NULL;
+
+ if (ei.perm_algo_len == 0) {
+ LOG_RAN_A_ENC(fi, LOGL_ERROR, "cannot start ciphering, no intersection between MSC-configured"
+ " and MS-supported A5 algorithms. MSC: 0x%02x MS: %s\n",
+ cm->geran.a5_encryption_mask, osmo_gsm48_classmark_a5_name(cm->classmark));
+ return NULL;
+ }
+
+ /* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth
+ * tokens. vec->kc was calculated from the GSM algorithm and is not
+ * necessarily a match for the UMTS AKA tokens. */
+ if (cm->geran.umts_aka)
+ osmo_auth_c3(ei.key, cm->vec->ck, cm->vec->ik);
+ else
+ memcpy(ei.key, cm->vec->kc, sizeof(cm->vec->kc));
+ ei.key_len = sizeof(cm->vec->kc);
+
+ /* Store chosen GERAN key where the caller asked it to be stored.
+ * alg_id remains unknown until we receive a Cipher Mode Complete from the BSC */
+ if (cm->geran.chosen_key) {
+ if (ei.key_len > sizeof(cm->geran.chosen_key->key)) {
+ LOG_RAN_A_ENC(fi, LOGL_ERROR, "Chosen key is larger than I can store\n");
+ return NULL;
+ }
+ memcpy(cm->geran.chosen_key->key, ei.key, ei.key_len);
+ cm->geran.chosen_key->key_len = ei.key_len;
+ }
+
+ LOG_RAN_A_ENC(fi, LOGL_DEBUG, "Tx BSSMAP CIPHER MODE COMMAND to BSC, %u ciphers (%s) key %s\n",
+ ei.perm_algo_len, osmo_hexdump_nospc(ei.perm_algo, ei.perm_algo_len),
+ osmo_hexdump_buf(buf, sizeof(buf), ei.key, ei.key_len, NULL, false));
+ return gsm0808_create_cipher(&ei, cm->geran.retrieve_imeisv ? &cipher_response_mode : NULL);
+}
+
+struct msgb *ran_a_make_handover_request(struct osmo_fsm_inst *log_fi, const struct ran_handover_request *n)
+{
+ struct sockaddr_storage ss;
+ struct gsm0808_handover_request r = {
+ .cell_identifier_serving = n->cell_id_serving,
+ .cell_identifier_target = n->cell_id_target,
+ .cause = n->bssap_cause,
+ .current_channel_type_1_present = n->current_channel_type_1_present,
+ .current_channel_type_1 = n->current_channel_type_1,
+
+ .speech_version_used = n->speech_version_used,
+
+ .chosen_encryption_algorithm_serving = n->geran.chosen_encryption->alg_id,
+
+ .old_bss_to_new_bss_info_raw = n->old_bss_to_new_bss_info_raw,
+ .old_bss_to_new_bss_info_raw_len = n->old_bss_to_new_bss_info_raw_len,
+
+ .imsi = n->imsi,
+ .codec_list_msc_preferred = n->codec_list_msc_preferred,
+ .call_id = n->call_id,
+ .global_call_reference = n->global_call_reference,
+ .global_call_reference_len = n->global_call_reference_len,
+ };
+
+ if (!n->geran.channel_type) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Channel Type required for encoding Handover Request in BSSAP\n");
+ return NULL;
+ }
+ r.channel_type = *n->geran.channel_type;
+
+ /* Encryption Information */
+ make_encrypt_info_perm_algo(log_fi, &r.encryption_information, n->geran.a5_encryption_mask, n->classmark);
+ if (n->geran.chosen_encryption && n->geran.chosen_encryption->key_len) {
+ if (n->geran.chosen_encryption->key_len > sizeof(r.encryption_information.key)) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Handover Request: invalid chosen encryption key size %u\n",
+ n->geran.chosen_encryption->key_len);
+ return NULL;
+ }
+ memcpy(r.encryption_information.key,
+ n->geran.chosen_encryption->key, n->geran.chosen_encryption->key_len);
+ r.encryption_information.key_len = n->geran.chosen_encryption->key_len;
+ }
+
+ if (n->classmark)
+ r.classmark_information = *n->classmark;
+
+ if (osmo_sockaddr_str_is_set(n->rtp_ran_local)) {
+ if (osmo_sockaddr_str_to_sockaddr(n->rtp_ran_local, &ss)) {
+ LOG_RAN_A_ENC(log_fi, LOGL_ERROR,
+ "Handover Request: invalid AoIP Transport Layer address/port: "
+ OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(n->rtp_ran_local));
+ return NULL;
+ }
+ r.aoip_transport_layer = &ss;
+ }
+
+ return gsm0808_create_handover_request(&r);
+}
+
+static struct msgb *ran_a_make_handover_request_ack(struct osmo_fsm_inst *caller_fi, const struct ran_handover_request_ack *r)
+{
+ struct sockaddr_storage ss;
+ struct gsm0808_handover_request_ack params = {
+ .l3_info = r->rr_ho_command,
+ .l3_info_len = r->rr_ho_command_len,
+ .chosen_channel_present = r->chosen_channel_present,
+ .chosen_channel = r->chosen_channel,
+ .chosen_encr_alg = r->chosen_encr_alg,
+ .chosen_speech_version = r->chosen_speech_version,
+ };
+
+ if (osmo_sockaddr_str_is_set(&r->remote_rtp)) {
+ osmo_sockaddr_str_to_sockaddr(&r->remote_rtp, &ss);
+ params.aoip_transport_layer = &ss;
+ }
+
+ return gsm0808_create_handover_request_ack2(&params);
+}
+
+struct msgb *ran_a_make_handover_command(struct osmo_fsm_inst *log_fi, const struct ran_handover_command *n)
+{
+ struct gsm0808_handover_command c = {
+ .l3_info = n->rr_ho_command,
+ .l3_info_len = n->rr_ho_command_len,
+ };
+
+ return gsm0808_create_handover_command(&c);
+}
+
+struct msgb *ran_a_make_handover_failure(struct osmo_fsm_inst *log_fi, const struct ran_msg *msg)
+{
+ struct gsm0808_handover_failure params = {
+ .cause = msg->handover_failure.cause,
+ };
+ return gsm0808_create_handover_failure(&params);
+}
+
+static struct msgb *_ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg)
+{
+
+ LOG_RAN_A_ENC(caller_fi, LOGL_DEBUG, "%s\n", ran_msg_type_name(ran_enc_msg->msg_type));
+
+ switch (ran_enc_msg->msg_type) {
+
+ case RAN_MSG_DTAP:
+ return ran_a_wrap_dtap(ran_enc_msg->dtap);
+
+ case RAN_MSG_CLASSMARK_REQUEST:
+ return gsm0808_create_classmark_request();
+
+ case RAN_MSG_CLEAR_COMMAND:
+ return gsm0808_create_clear_command2(ran_enc_msg->clear_command.gsm0808_cause,
+ ran_enc_msg->clear_command.csfb_ind);
+
+ case RAN_MSG_ASSIGNMENT_COMMAND:
+ return ran_a_make_assignment_command(caller_fi, &ran_enc_msg->assignment_command);
+
+ case RAN_MSG_CIPHER_MODE_COMMAND:
+ return ran_a_make_cipher_mode_command(caller_fi, &ran_enc_msg->cipher_mode_command);
+
+ case RAN_MSG_HANDOVER_REQUIRED_REJECT:
+ return gsm0808_create_handover_required_reject(&ran_enc_msg->handover_required_reject);
+
+ case RAN_MSG_HANDOVER_REQUEST:
+ return ran_a_make_handover_request(caller_fi, &ran_enc_msg->handover_request);
+
+ case RAN_MSG_HANDOVER_REQUEST_ACK:
+ return ran_a_make_handover_request_ack(caller_fi, &ran_enc_msg->handover_request_ack);
+
+ case RAN_MSG_HANDOVER_COMMAND:
+ return ran_a_make_handover_command(caller_fi, &ran_enc_msg->handover_command);
+
+ case RAN_MSG_HANDOVER_SUCCEEDED:
+ return gsm0808_create_handover_succeeded();
+
+ case RAN_MSG_HANDOVER_FAILURE:
+ return ran_a_make_handover_failure(caller_fi, ran_enc_msg);
+
+ default:
+ LOG_RAN_A_ENC(caller_fi, LOGL_ERROR, "Unimplemented RAN-encode message type: %s\n",
+ ran_msg_type_name(ran_enc_msg->msg_type));
+ return NULL;
+ }
+}
+
+struct msgb *ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg)
+{
+ struct msgb *msg = _ran_a_encode(caller_fi, ran_enc_msg);
+
+ if (!msg)
+ return NULL;
+
+ msg->l2h = msg->data;
+
+ /* some consistency checks to ensure we don't send invalid length */
+ switch (msg->l2h[0]) {
+ case BSSAP_MSG_DTAP:
+ OSMO_ASSERT(msgb_l2len(msg) == msg->l2h[2] + 3);
+ break;
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ OSMO_ASSERT(msgb_l2len(msg) == msg->l2h[1] + 2);
+ break;
+ default:
+ break;
+ }
+
+ return msg;
+}
+
+/* Return 1 for a RESET, 2 for a RESET ACK message, 0 otherwise */
+enum reset_msg_type bssmap_is_reset_msg(const struct sccp_ran_inst *sri, const struct msgb *l2)
+{
+ struct bssmap_header *bs = (struct bssmap_header *)msgb_l2(l2);
+
+ if (!bs
+ || msgb_l2len(l2) < (sizeof(*bs) + 1)
+ || bs->type != BSSAP_MSG_BSS_MANAGEMENT)
+ return SCCP_RAN_MSG_NON_RESET;
+
+ switch (l2->l2h[sizeof(*bs)]) {
+ case BSS_MAP_MSG_RESET:
+ return SCCP_RAN_MSG_RESET;
+ case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+ return SCCP_RAN_MSG_RESET_ACK;
+ default:
+ return SCCP_RAN_MSG_NON_RESET;
+ }
+}
+
+struct msgb *bssmap_make_reset_msg(const struct sccp_ran_inst *sri, enum reset_msg_type type)
+{
+ switch (type) {
+ case SCCP_RAN_MSG_RESET:
+ return gsm0808_create_reset();
+ case SCCP_RAN_MSG_RESET_ACK:
+ return gsm0808_create_reset_ack();
+ default:
+ return NULL;
+ }
+}
+
+struct msgb *bssmap_make_paging_msg(const struct sccp_ran_inst *sri, const struct gsm0808_cell_id *page_cell_id,
+ const char *imsi, uint32_t tmsi, enum paging_cause cause)
+{
+ struct gsm0808_cell_id_list2 cil;
+ gsm0808_cell_id_to_list(&cil, page_cell_id);
+ return gsm0808_create_paging2(imsi, tmsi == GSM_RESERVED_TMSI ? NULL : &tmsi, &cil, NULL);
+}
+
+const char *bssmap_msg_name(const struct sccp_ran_inst *sri, const struct msgb *l2)
+{
+ struct bssmap_header *bs;
+
+ if (!l2->l2h)
+ return "?";
+
+ bs = (struct bssmap_header *)msgb_l2(l2);
+ switch (bs->type) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ return gsm0808_bssmap_name(l2->l2h[0]);
+ case BSSAP_MSG_DTAP:
+ return "DTAP";
+ default:
+ return "?";
+ }
+}
diff --git a/src/libmsc/ran_msg_iu.c b/src/libmsc/ran_msg_iu.c
new file mode 100644
index 000000000..79b0d5cb9
--- /dev/null
+++ b/src/libmsc/ran_msg_iu.c
@@ -0,0 +1,505 @@
+/* RANAP encoding and decoding for MSC */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <asn1c/asn1helpers.h>
+
+#include <osmocom/core/prim.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/ranap/ranap_common_cn.h>
+#include <osmocom/ranap/ranap_msg_factory.h>
+#include <osmocom/ranap/iu_helpers.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/msc_common.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/ran_msg_iu.h>
+
+/* Implement the extern talloc_asn1_ctx from libasn1c as talloc ctx for ASN.1 message composition */
+void *talloc_asn1_ctx = NULL;
+
+/* Implement the extern asn_debug from libasn1c to indicate whether to print asn.1 debug messages. */
+int asn_debug = 0;
+
+/* Implement the extern asn1_xer_print to indicate whether the ASN.1 binary code decoded and encoded during Iu
+ * communication should be logged to stderr (see asn.1 generated code in osmo-iuh). */
+int asn1_xer_print = 0;
+
+#define LOG_RAN_IU_DEC(RAN_DEC, level, fmt, args...) \
+ LOG_RAN_DEC(RAN_DEC, DIUCS, level, "RANAP: " fmt, ## args)
+
+#define LOG_RAN_IU_ENC(FI, level, fmt, args...) \
+ LOG_RAN_ENC(FI, DIUCS, level, "RANAP: " fmt, ## args)
+
+static void ran_iu_decode_l3_initial(struct ran_dec *ran_iu_decode, const RANAP_InitialUE_MessageIEs_t *ies, const char *msg_name)
+{
+ struct msgb *ran = msgb_alloc(256, msg_name);
+ struct ran_msg ran_dec_msg;
+
+ struct osmo_plmn_id plmn;
+
+ if (ies->lai.pLMNidentity.size < 3) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Too short PLMNidentity in RANAP InitialUE message\n");
+ return;
+ }
+ osmo_plmn_from_bcd(ies->lai.pLMNidentity.buf, &plmn);
+
+ struct gsm0808_cell_id cid = {
+ .id_discr = CELL_IDENT_LAI,
+ .id.lai_and_lac = {
+ .plmn = plmn,
+ .lac = asn1str_to_u16(&ies->lai.lAC),
+ },
+ };
+
+ /* TODO: really necessary to copy the RAN PDU?? */
+ ran->l3h = msgb_put(ran, ies->nas_pdu.size);
+ memcpy(ran->l3h, ies->nas_pdu.buf, ies->nas_pdu.size);
+
+ ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_COMPL_L3,
+ .msg_name = msg_name,
+ .compl_l3 = {
+ .cell_id = &cid,
+ .msg = ran,
+ },
+ };
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+
+ msgb_free(ran);
+}
+
+static void ran_iu_decode_l3(struct ran_dec *ran_iu_decode, const RANAP_NAS_PDU_t *nas_pdu, const char *msg_name)
+{
+ struct msgb *ran = msgb_alloc(256, msg_name);
+ struct ran_msg ran_dec_msg;
+
+ /* TODO: really necessary to copy the RAN PDU?? */
+ ran->l3h = msgb_put(ran, nas_pdu->size);
+ memcpy(ran->l3h, nas_pdu->buf, nas_pdu->size);
+
+ ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_DTAP,
+ .msg_name = msg_name,
+ .dtap = ran,
+ };
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+
+ msgb_free(ran);
+}
+
+static void ran_iu_decode_err(struct ran_dec *ran_iu_decode, const RANAP_ErrorIndicationIEs_t *ies)
+{
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Rx Error Indication (%s)\n",
+ (ies->presenceMask & ERRORINDICATIONIES_RANAP_CAUSE_PRESENT)?
+ ranap_cause_str(&ies->cause) : "no cause specified");
+}
+
+static int ran_iu_decode_rab_assignment_response_decode_setup_ies(struct ran_dec *ran_iu_decode,
+ struct ran_msg *ran_dec_msg,
+ const RANAP_RAB_SetupOrModifiedItemIEs_t *setup_ies)
+{
+ const RANAP_RAB_SetupOrModifiedItem_t *item;
+ const RANAP_TransportLayerAddress_t *transp_layer_addr;
+ const RANAP_IuTransportAssociation_t *transp_assoc;
+ uint16_t port = 0;
+ char addr[INET_ADDRSTRLEN];
+ uint8_t rab_id;
+
+ item = &setup_ies->raB_SetupOrModifiedItem;
+
+ rab_id = item->rAB_ID.buf[0];
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_DEBUG, "Received RAB assignment response for rab_id=%d\n", rab_id);
+
+ if (!(item->iuTransportAssociation && item->transportLayerAddress)) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "RAB Assignment Response does not contain RAB information\n");
+ return -1;
+ }
+
+ transp_layer_addr = item->transportLayerAddress;
+ transp_assoc = item->iuTransportAssociation;
+
+ if (ranap_transp_assoc_decode(&port, transp_assoc)) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Unable to decode RTP port in RAB Assignment Response\n");
+ return -1;
+ }
+
+ if (ranap_transp_layer_addr_decode(addr, sizeof(addr), transp_layer_addr)) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Unable to decode IP-Address in RAB Assignment Response\n");
+ return -1;
+ }
+
+ *ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_ASSIGNMENT_COMPLETE,
+ .msg_name = "RANAP RAB Assignment Response",
+ .assignment_complete = {
+ .codec = CODEC_AMR_8000_1,
+ },
+ };
+ if (osmo_sockaddr_str_from_str(&ran_dec_msg->assignment_complete.remote_rtp, addr, port)) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Assignment Complete: unable to decode remote RTP IP address %s\n",
+ osmo_quote_str(addr, -1));
+ return -1;
+ }
+ return 0;
+}
+
+static void ran_iu_decode_rab_assignment_response(struct ran_dec *ran_iu_decode, const RANAP_RAB_AssignmentResponseIEs_t *ies)
+{
+ int rc;
+ RANAP_IE_t *ranap_ie;
+ RANAP_RAB_SetupOrModifiedItemIEs_t setup_ies;
+ struct ran_msg ran_dec_msg;
+ bool free_ies = false;
+
+ if (!(ies->presenceMask & RAB_ASSIGNMENTRESPONSEIES_RANAP_RAB_SETUPORMODIFIEDLIST_PRESENT)) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "RAB Assignment Response does not contain RAB information\n");
+ goto failure;
+ }
+
+ /* So far we assign a single RAB at a time, so it should not be necessary to iterate over the list of
+ * SetupOrModifiedList IEs and handle each one. */
+ ranap_ie = ies->raB_SetupOrModifiedList.raB_SetupOrModifiedList_ies.list.array[0];
+
+ rc = ranap_decode_rab_setupormodifieditemies_fromlist(&setup_ies, &ranap_ie->value);
+ if (rc) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Error in ranap_decode_rab_setupormodifieditemies(): rc=%d\n", rc);
+ goto failure;
+ }
+ free_ies = true;
+
+ if (!ran_iu_decode_rab_assignment_response_decode_setup_ies(ran_iu_decode, &ran_dec_msg, &setup_ies))
+ goto success;
+
+failure:
+ ran_dec_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_ASSIGNMENT_FAILURE,
+ .msg_name = "RANAP RAB Assignment Response: Failure",
+ .assignment_failure = {
+ .bssap_cause = RAN_MSG_BSSAP_CAUSE_UNSET,
+ .rr_cause = GSM48_RR_CAUSE_ABNORMAL_UNSPEC,
+ },
+ };
+
+success:
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+
+ if (free_ies)
+ ranap_free_rab_setupormodifieditemies(&setup_ies);
+}
+
+static void ran_iu_decode_security_mode_complete(struct ran_dec *ran_iu_decode)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CIPHER_MODE_COMPLETE,
+ .msg_name = "RANAP SecurityModeControl successfulOutcome",
+ };
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+}
+
+static void ran_iu_decode_security_mode_reject(struct ran_dec *ran_iu_decode)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CIPHER_MODE_REJECT,
+ .msg_name = "RANAP SecurityModeControl unsuccessfulOutcome",
+ .cipher_mode_reject = {
+ .bssap_cause = RAN_MSG_BSSAP_CAUSE_UNSET,
+ },
+ };
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+}
+
+static void ran_iu_decode_release_request(struct ran_dec *ran_iu_decode)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CLEAR_REQUEST,
+ .msg_name = "RANAP Iu ReleaseRequest",
+ .clear_request = {
+ .bssap_cause = RAN_MSG_BSSAP_CAUSE_UNSET,
+ },
+ };
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+}
+
+static void ran_iu_decode_release_complete(struct ran_dec *ran_iu_decode)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_CLEAR_COMPLETE,
+ .msg_name = "RANAP Iu Release successfulOutcome",
+ };
+ ran_decoded(ran_iu_decode, &ran_dec_msg);
+}
+
+static void ran_iu_decode_ranap_msg(void *_ran_dec, ranap_message *message)
+{
+ struct ran_dec *ran_iu_decode = _ran_dec;
+
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_DEBUG, "dir=%u proc=%u\n", message->direction, message->procedureCode);
+
+ switch (message->procedureCode) {
+
+ case RANAP_ProcedureCode_id_InitialUE_Message:
+ ran_iu_decode_l3_initial(ran_iu_decode, &message->msg.initialUE_MessageIEs, "RANAP InitialUE RAN PDU");
+ return;
+
+ case RANAP_ProcedureCode_id_DirectTransfer:
+ ran_iu_decode_l3(ran_iu_decode, &message->msg.directTransferIEs.nas_pdu, "RANAP DirectTransfer RAN PDU");
+ return;
+
+ case RANAP_ProcedureCode_id_SecurityModeControl:
+ switch (message->direction) {
+ case RANAP_RANAP_PDU_PR_successfulOutcome:
+ ran_iu_decode_security_mode_complete(ran_iu_decode);
+ return;
+ case RANAP_RANAP_PDU_PR_unsuccessfulOutcome:
+ ran_iu_decode_security_mode_reject(ran_iu_decode);
+ return;
+ default:
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR,
+ "Received SecurityModeControl: unexpected RANAP ProcedureCode: %d\n",
+ message->direction);
+ return;
+ }
+
+ case RANAP_ProcedureCode_id_RAB_Assignment:
+ /* This should always be a RANAP_RANAP_PDU_PR_outcome. No need to check for that. */
+ ran_iu_decode_rab_assignment_response(ran_iu_decode, &message->msg.raB_AssignmentResponseIEs);
+ return;
+
+ case RANAP_ProcedureCode_id_Iu_ReleaseRequest:
+ ran_iu_decode_release_request(ran_iu_decode);
+ return;
+
+ case RANAP_ProcedureCode_id_Iu_Release:
+ if (message->direction != RANAP_RANAP_PDU_PR_successfulOutcome) {
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Received Iu_Release: expected successfulOutcome, got %d\n",
+ message->direction);
+ return;
+ }
+ ran_iu_decode_release_complete(ran_iu_decode);
+ return;
+
+ case RANAP_ProcedureCode_id_ErrorIndication:
+ ran_iu_decode_err(ran_iu_decode, &message->msg.errorIndicationIEs);
+ return;
+
+ default:
+ LOG_RAN_IU_DEC(ran_iu_decode, LOGL_ERROR, "Received unhandled RANAP Procedure Code %d\n", message->procedureCode);
+ return;
+ }
+}
+
+int ran_iu_decode_l2(struct ran_dec *ran_iu_decode, struct msgb *ranap)
+{
+ return ranap_cn_rx_co(ran_iu_decode_ranap_msg, ran_iu_decode, msgb_l2(ranap), msgb_l2len(ranap));
+}
+
+/* Create a RANAP Initiating DirectTransfer message containing the given DTAP as RAN PDU, and return the resulting
+ * AN-APDU to be forwarded via E-interface. */
+static struct msgb *ran_iu_wrap_dtap(struct msgb *dtap)
+{
+ struct msgb *an_apdu;
+ an_apdu = ranap_new_msg_dt(0, dtap->data, msgb_length(dtap));
+ an_apdu->l2h = an_apdu->data;
+ msgb_free(dtap);
+ return an_apdu;
+}
+
+static struct msgb *ran_iu_make_rab_assignment(struct osmo_fsm_inst *caller_fi, const struct ran_assignment_command *ac)
+{
+ struct msgb *msg;
+ bool use_x213_nsap;
+ uint32_t cn_rtp_ip;
+ static uint8_t next_rab_id = 1;
+ uint8_t rab_id = next_rab_id;
+
+ next_rab_id ++;
+ if (!next_rab_id)
+ next_rab_id = 1;
+
+ cn_rtp_ip = osmo_htonl(inet_addr(ac->cn_rtp->ip));
+
+ if (cn_rtp_ip == INADDR_NONE) {
+ LOG_RAN_IU_ENC(caller_fi, LOGL_ERROR, "Error during RAB Assignment: invalid RTP IP-Address\n");
+ return NULL;
+ }
+ if (ac->cn_rtp->port == 0) {
+ LOG_RAN_IU_ENC(caller_fi, LOGL_ERROR, "Error during RAB Assignment: invalid RTP port\n");
+ return NULL;
+ }
+
+ use_x213_nsap = (ac->rab_assign_addr_enc == NSAP_ADDR_ENC_X213);
+ LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "RAB Assignment: rab_id=%d, rtp=" OSMO_SOCKADDR_STR_FMT ", use_x213_nsap=%d\n",
+ rab_id, OSMO_SOCKADDR_STR_FMT_ARGS(ac->cn_rtp), use_x213_nsap);
+
+ msg = ranap_new_msg_rab_assign_voice(rab_id, cn_rtp_ip, ac->cn_rtp->port, use_x213_nsap);
+ msg->l2h = msg->data;
+
+ return msg;
+}
+
+static struct msgb *ran_iu_make_security_mode_command(struct osmo_fsm_inst *caller_fi,
+ const struct ran_cipher_mode_command *cm)
+{
+
+ LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "Tx RANAP SECURITY MODE COMMAND to RNC, ik %s\n",
+ osmo_hexdump_nospc(cm->vec->ik, 16));
+ return ranap_new_msg_sec_mod_cmd(cm->vec->ik, NULL, RANAP_KeyStatus_new);
+}
+
+
+static struct msgb *ran_iu_make_release_command(struct osmo_fsm_inst *caller_fi,
+ const struct ran_clear_command *ccmd)
+{
+ static const struct RANAP_Cause cause = {
+ .present = RANAP_Cause_PR_radioNetwork,
+ .choice.radioNetwork = RANAP_CauseRadioNetwork_release_due_to_utran_generated_reason,
+ /* TODO: set various causes depending on the ran_clear_command cause value */
+ };
+ return ranap_new_msg_iu_rel_cmd(&cause);
+}
+
+struct msgb *ran_iu_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg)
+{
+ LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "%s\n", ran_msg_type_name(ran_enc_msg->msg_type));
+
+ switch (ran_enc_msg->msg_type) {
+
+ case RAN_MSG_DTAP:
+ return ran_iu_wrap_dtap(ran_enc_msg->dtap);
+
+ // TODO: RAN_MSG_CLASSMARK_REQUEST ??
+
+ case RAN_MSG_CIPHER_MODE_COMMAND:
+ return ran_iu_make_security_mode_command(caller_fi, &ran_enc_msg->cipher_mode_command);
+
+ case RAN_MSG_ASSIGNMENT_COMMAND:
+ return ran_iu_make_rab_assignment(caller_fi, &ran_enc_msg->assignment_command);
+
+ case RAN_MSG_COMMON_ID:
+ return ranap_new_msg_common_id(ran_enc_msg->common_id.imsi);
+
+ case RAN_MSG_CLEAR_COMMAND:
+ return ran_iu_make_release_command(caller_fi, &ran_enc_msg->clear_command);
+
+ default:
+ LOG_RAN_IU_ENC(caller_fi, LOGL_ERROR, "Message type not implemented: %s\n",
+ ran_msg_type_name(ran_enc_msg->msg_type));
+ return NULL;
+ }
+}
+
+/* Entry point for connection-less RANAP message */
+static void ranap_handle_cl(void *ctx, ranap_message *message)
+{
+ int *rc = ctx;
+ *rc = SCCP_RAN_MSG_NON_RESET;
+
+ if (message->procedureCode != RANAP_ProcedureCode_id_Reset)
+ return;
+
+ switch (message->direction) {
+ case RANAP_RANAP_PDU_PR_initiatingMessage:
+ *rc = SCCP_RAN_MSG_RESET;
+ return;
+ case RANAP_RANAP_PDU_PR_successfulOutcome:
+ *rc = SCCP_RAN_MSG_RESET_ACK;
+ return;
+ default:
+ return;
+ }
+}
+
+enum reset_msg_type ranap_is_reset_msg(const struct sccp_ran_inst *sri, const struct msgb *l2)
+{
+ int ret = SCCP_RAN_MSG_NON_RESET;
+ int rc;
+
+ rc = ranap_cn_rx_cl(ranap_handle_cl, &ret, msgb_l2(l2), msgb_l2len(l2));
+ if (rc)
+ return 0;
+ return ret;
+}
+
+struct msgb *ranap_make_reset_msg(const struct sccp_ran_inst *sri, enum reset_msg_type type)
+{
+ const RANAP_Cause_t cause = {
+ .present = RANAP_Cause_PR_protocol,
+ .choice = {
+ .protocol = RANAP_CauseProtocol_message_not_compatible_with_receiver_state,
+ },
+ };
+ switch (type) {
+ case SCCP_RAN_MSG_RESET:
+ return ranap_new_msg_reset(RANAP_CN_DomainIndicator_cs_domain, &cause);
+ case SCCP_RAN_MSG_RESET_ACK:
+ return ranap_new_msg_reset_ack(RANAP_CN_DomainIndicator_cs_domain, NULL);
+ default:
+ return NULL;
+ }
+}
+
+static e_RANAP_PagingCause ranap_paging_cause_from_msc(enum paging_cause cause)
+{
+ switch (cause) {
+ default:
+ case PAGING_CAUSE_UNSPECIFIED:
+ case PAGING_CAUSE_CALL_CONVERSATIONAL:
+ return RANAP_PagingCause_terminating_conversational_call;
+ case PAGING_CAUSE_CALL_STREAMING:
+ return RANAP_PagingCause_terminating_streaming_call;
+ case PAGING_CAUSE_CALL_INTERACTIVE:
+ return RANAP_PagingCause_terminating_interactive_call;
+ case PAGING_CAUSE_CALL_BACKGROUND:
+ return RANAP_PagingCause_terminating_background_call;
+ case PAGING_CAUSE_SIGNALLING_LOW_PRIO:
+ return RANAP_PagingCause_terminating_low_priority_signalling;
+ case PAGING_CAUSE_SIGNALLING_HIGH_PRIO:
+ return RANAP_PagingCause_terminating_high_priority_signalling;
+ }
+}
+
+struct msgb *ranap_make_paging_msg(const struct sccp_ran_inst *sri, const struct gsm0808_cell_id *page_cell_id,
+ const char *imsi, uint32_t tmsi, enum paging_cause cause)
+{
+ return ranap_new_msg_paging_cmd(imsi, tmsi == GSM_RESERVED_TMSI ? NULL : &tmsi, false,
+ ranap_paging_cause_from_msc(cause));
+}
+
+const char *ranap_msg_name(const struct sccp_ran_inst *sri, const struct msgb *l2)
+{
+ uint8_t msgt;
+ uint8_t procedure;
+ static char buf[32];
+ if (!l2->l2h)
+ return "?";
+
+ msgt = l2->l2h[0];
+ procedure = l2->l2h[1];
+
+ snprintf(buf, sizeof(buf), "type %u procedureCode %u", msgt, procedure);
+ return buf;
+}
diff --git a/src/libmsc/ran_peer.c b/src/libmsc/ran_peer.c
new file mode 100644
index 000000000..ac2bb4f96
--- /dev/null
+++ b/src/libmsc/ran_peer.c
@@ -0,0 +1,659 @@
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/ran_conn.h>
+#include <osmocom/msc/cell_id_list.h>
+
+static struct osmo_fsm ran_peer_fsm;
+
+static __attribute__((constructor)) void ran_peer_init()
+{
+ OSMO_ASSERT( osmo_fsm_register(&ran_peer_fsm) == 0);
+}
+
+/* Allocate a RAN peer with FSM instance. To deallocate, call osmo_fsm_inst_term(ran_peer->fi). */
+static struct ran_peer *ran_peer_alloc(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr)
+{
+ struct ran_peer *rp;
+ struct osmo_fsm_inst *fi;
+ char *sccp_addr;
+ char *pos;
+
+ fi = osmo_fsm_inst_alloc(&ran_peer_fsm, sri, NULL, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(fi);
+
+ /* Unfortunately, osmo_sccp_inst_addr_name() returns "RI=SSN_PC,PC=0.24.1,SSN=BSSAP" but neither commas nor
+ * full-stops are allowed as FSM inst id. Make it "RI=SSN_PC:PC-0-24-1:SSN-BSSAP". */
+ sccp_addr = osmo_sccp_inst_addr_name(sri->sccp, peer_addr);
+ for (pos = sccp_addr; *pos; pos++) {
+ if (*pos == ',')
+ *pos = ':';
+ else if (*pos == '.' || *pos == '=')
+ *pos = '-';
+ }
+ osmo_fsm_inst_update_id_f(fi, "%s:%s", osmo_rat_type_name(sri->ran->type), sccp_addr);
+
+ rp = talloc_zero(fi, struct ran_peer);
+ OSMO_ASSERT(rp);
+ *rp = (struct ran_peer){
+ .sri = sri,
+ .peer_addr = *peer_addr,
+ .fi = fi,
+ };
+ INIT_LLIST_HEAD(&rp->cells_seen);
+ fi->priv = rp;
+
+ llist_add(&rp->entry, &sri->ran_peers);
+
+ return rp;
+}
+
+struct ran_peer *ran_peer_find_or_create(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr)
+{
+ struct ran_peer *rp = ran_peer_find(sri, peer_addr);
+ if (rp)
+ return rp;
+ return ran_peer_alloc(sri, peer_addr);
+}
+
+struct ran_peer *ran_peer_find(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr)
+{
+ struct ran_peer *rp;
+ llist_for_each_entry(rp, &sri->ran_peers, entry) {
+ if (osmo_sccp_addr_ri_cmp(peer_addr, &rp->peer_addr))
+ continue;
+ return rp;
+ }
+ return NULL;
+}
+
+void ran_peer_cells_seen_add(struct ran_peer *ran_peer, const struct gsm0808_cell_id *cid)
+{
+ if (!cell_id_list_add_cell(ran_peer, &ran_peer->cells_seen, cid))
+ return;
+ LOG_RAN_PEER_CAT(ran_peer, DPAG, LOGL_NOTICE, "Added seen cell to this RAN peer: %s\n",
+ gsm0808_cell_id_name(cid));
+}
+
+static const struct osmo_tdef_state_timeout ran_peer_fsm_timeouts[32] = {
+ [RAN_PEER_ST_WAIT_RX_RESET_ACK] = { .T = -1 },
+ [RAN_PEER_ST_DISCARDING] = { .T = -2 },
+};
+
+#define ran_peer_state_chg(RAN_PEER, NEXT_STATE) \
+ osmo_tdef_fsm_inst_state_chg((RAN_PEER)->fi, NEXT_STATE, ran_peer_fsm_timeouts, g_sccp_tdefs, 5)
+
+void ran_peer_discard_all_conns(struct ran_peer *rp)
+{
+ struct ran_conn *conn, *next;
+
+ ran_peer_for_each_ran_conn_safe(conn, next, rp) {
+ ran_conn_discard(conn);
+ }
+}
+
+/* Drop all SCCP connections for this ran_peer, respond with RESET ACKNOWLEDGE and move to READY state. */
+static void ran_peer_rx_reset(struct ran_peer *rp)
+{
+ struct msgb *reset_ack;
+
+ ran_peer_discard_all_conns(rp);
+
+ reset_ack = rp->sri->ran->sccp_ran_ops.make_reset_msg(rp->sri, SCCP_RAN_MSG_RESET_ACK);
+
+ if (!reset_ack) {
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to compose RESET ACKNOWLEDGE message\n");
+ ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
+ return;
+ }
+
+ if (sccp_ran_down_l2_cl(rp->sri, &rp->peer_addr, reset_ack)) {
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to send RESET ACKNOWLEDGE message\n");
+ ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
+ return;
+ }
+
+ LOG_RAN_PEER(rp, LOGL_INFO, "Sent RESET ACKNOWLEDGE\n");
+
+ ran_peer_state_chg(rp, RAN_PEER_ST_READY);
+}
+
+void ran_peer_reset(struct ran_peer *rp)
+{
+ struct msgb *reset;
+
+ ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET_ACK);
+ ran_peer_discard_all_conns(rp);
+
+ reset = rp->sri->ran->sccp_ran_ops.make_reset_msg(rp->sri, SCCP_RAN_MSG_RESET);
+
+ if (!reset) {
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to compose RESET message\n");
+ ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
+ return;
+ }
+
+ if (sccp_ran_down_l2_cl(rp->sri, &rp->peer_addr, reset)) {
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to send RESET message\n");
+ ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
+ return;
+ }
+}
+
+void ran_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ran_peer *rp = fi->priv;
+ struct ran_peer_ev_ctx *ctx = data;
+ struct msgb *msg = ctx->msg;
+
+ switch (event) {
+ case RAN_PEER_EV_MSG_UP_CL:
+ switch (rp->sri->ran->sccp_ran_ops.is_reset_msg(rp->sri, msg)) {
+ case 1:
+ osmo_fsm_inst_dispatch(fi, RAN_PEER_EV_RX_RESET, msg);
+ return;
+ case 2:
+ osmo_fsm_inst_dispatch(fi, RAN_PEER_EV_RX_RESET_ACK, msg);
+ return;
+ default:
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled ConnectionLess message received: %s\n",
+ rp->sri->ran->sccp_ran_ops.msg_name(rp->sri, msg));
+ return;
+ }
+
+ default:
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
+ return;
+ }
+}
+
+void clear_and_disconnect(struct ran_peer *rp, uint32_t conn_id)
+{
+ struct msgb *clear;
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_CLEAR_COMMAND,
+ .clear_command = {
+ .gsm0808_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ },
+ };
+
+ clear = rp->sri->ran->ran_encode(rp->fi, &ran_enc_msg);
+ if (!clear
+ || sccp_ran_down_l2_co(rp->sri, conn_id, clear))
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Cannot sent Clear command\n");
+
+ sccp_ran_disconnect(rp->sri, conn_id, 0);
+}
+
+void ran_peer_st_wait_rx_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ran_peer *rp = fi->priv;
+ struct ran_peer_ev_ctx *ctx;
+
+ switch (event) {
+
+ case RAN_PEER_EV_MSG_UP_CO:
+ case RAN_PEER_EV_MSG_UP_CO_INITIAL:
+ ctx = data;
+ OSMO_ASSERT(ctx);
+
+ if (rp->sri->ignore_missing_reset) {
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Receiving CO message on RAN peer that has not done a proper RESET yet."
+ " Accepting RAN peer implicitly (legacy compat)\n");
+ ran_peer_state_chg(rp, RAN_PEER_ST_READY);
+ osmo_fsm_inst_dispatch(rp->fi, event, data);
+ return;
+ }
+
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Receiving CO message on RAN peer that has not done a proper RESET yet."
+ " Disconnecting on incoming message, sending RESET to RAN peer.\n");
+ /* No valid RESET procedure has happened here yet. Usually, we're expecting the RAN peer (BSC,
+ * RNC) to first send a RESET message before sending Connection Oriented messages. So if we're
+ * getting a CO message, likely we've just restarted or something. Send a RESET to the peer. */
+
+ /* Make sure the MS / UE properly disconnects. */
+ clear_and_disconnect(rp, ctx->conn_id);
+
+ ran_peer_reset(rp);
+ return;
+
+ case RAN_PEER_EV_RX_RESET:
+ ran_peer_rx_reset(rp);
+ return;
+
+ default:
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
+ return;
+ }
+}
+
+void ran_peer_st_wait_rx_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ran_peer *rp = fi->priv;
+ struct ran_peer_ev_ctx *ctx;
+
+ switch (event) {
+
+ case RAN_PEER_EV_RX_RESET_ACK:
+ ran_peer_state_chg(rp, RAN_PEER_ST_READY);
+ return;
+
+ case RAN_PEER_EV_MSG_UP_CO:
+ case RAN_PEER_EV_MSG_UP_CO_INITIAL:
+ ctx = data;
+ OSMO_ASSERT(ctx);
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Receiving CO message on RAN peer that has not done a proper RESET yet."
+ " Disconnecting on incoming message, sending RESET to RAN peer.\n");
+ sccp_ran_disconnect(rp->sri, ctx->conn_id, 0);
+ /* No valid RESET procedure has happened here yet. */
+ ran_peer_reset(rp);
+ return;
+
+ case RAN_PEER_EV_RX_RESET:
+ ran_peer_rx_reset(rp);
+ return;
+
+ default:
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
+ return;
+ }
+}
+
+static struct ran_conn *new_incoming_conn(struct ran_peer *rp, uint32_t conn_id)
+{
+ struct gsm_network *net = rp->sri->user_data;
+ struct msub *msub;
+ struct msc_i *msc_i;
+ struct msc_a *msc_a;
+ struct ran_conn *ran_conn;
+
+ msub = msub_alloc(net);
+ OSMO_ASSERT(msub);
+ msc_i = msc_i_alloc(msub, rp->sri->ran);
+ OSMO_ASSERT(msc_i);
+
+ ran_conn = ran_conn_create_incoming(rp, conn_id);
+ if (!ran_conn) {
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Cannot allocate ran_conn\n");
+ return NULL;
+ }
+ msc_i_set_ran_conn(msc_i, ran_conn);
+
+ msc_a = msc_a_alloc(msub, rp->sri->ran);
+ OSMO_ASSERT(msc_a);
+
+ return msc_i->ran_conn;
+}
+
+void ran_peer_st_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ran_peer *rp = fi->priv;
+ struct ran_peer_ev_ctx *ctx;
+ struct ran_conn *conn;
+ struct an_apdu an_apdu;
+
+ switch (event) {
+
+ case RAN_PEER_EV_MSG_UP_CO_INITIAL:
+ ctx = data;
+ OSMO_ASSERT(ctx)
+ OSMO_ASSERT(!ctx->conn);
+ OSMO_ASSERT(ctx->msg);
+
+ conn = new_incoming_conn(rp, ctx->conn_id);
+ if (!conn)
+ return;
+ if (!conn->msc_role) {
+ LOG_RAN_PEER(rp, LOGL_ERROR,
+ "Rx CO Initial message on conn that is not associated with any MSC role\n");
+ return;
+ }
+
+
+ an_apdu = (struct an_apdu){
+ .an_proto = rp->sri->ran->an_proto,
+ .msg = ctx->msg,
+ };
+
+ osmo_fsm_inst_dispatch(conn->msc_role, MSC_EV_FROM_RAN_COMPLETE_LAYER_3, &an_apdu);
+ return;
+
+ case RAN_PEER_EV_MSG_UP_CO:
+ ctx = data;
+ OSMO_ASSERT(ctx);
+ OSMO_ASSERT(ctx->conn);
+ OSMO_ASSERT(ctx->msg);
+
+ if (!ctx->conn->msc_role) {
+ LOG_RAN_PEER(rp, LOGL_ERROR,
+ "Rx CO message on conn that is not associated with any MSC role\n");
+ return;
+ }
+
+ an_apdu = (struct an_apdu){
+ .an_proto = rp->sri->ran->an_proto,
+ .msg = ctx->msg,
+ };
+
+ osmo_fsm_inst_dispatch(ctx->conn->msc_role, MSC_EV_FROM_RAN_UP_L2, &an_apdu);
+ return;
+
+ case RAN_PEER_EV_MSG_DOWN_CO_INITIAL:
+ ctx = data;
+ OSMO_ASSERT(ctx);
+ OSMO_ASSERT(ctx->msg);
+ sccp_ran_down_l2_co_initial(rp->sri, &rp->peer_addr, ctx->conn_id, ctx->msg);
+ return;
+
+ case RAN_PEER_EV_MSG_DOWN_CO:
+ ctx = data;
+ OSMO_ASSERT(ctx);
+ OSMO_ASSERT(ctx->msg);
+ sccp_ran_down_l2_co(rp->sri, ctx->conn_id, ctx->msg);
+ return;
+
+ case RAN_PEER_EV_MSG_DOWN_CL:
+ OSMO_ASSERT(data);
+ sccp_ran_down_l2_cl(rp->sri, &rp->peer_addr, (struct msgb*)data);
+ return;
+
+ case RAN_PEER_EV_RX_RESET:
+ ran_peer_rx_reset(rp);
+ return;
+
+ default:
+ LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
+ return;
+ }
+}
+
+static int ran_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct ran_peer *rp = fi->priv;
+ ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
+ return 0;
+}
+
+void ran_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct ran_peer *rp = fi->priv;
+ ran_peer_discard_all_conns(rp);
+ llist_del(&rp->entry);
+}
+
+static const struct value_string ran_peer_fsm_event_names[] = {
+ OSMO_VALUE_STRING(RAN_PEER_EV_MSG_UP_CL),
+ OSMO_VALUE_STRING(RAN_PEER_EV_MSG_UP_CO_INITIAL),
+ OSMO_VALUE_STRING(RAN_PEER_EV_MSG_UP_CO),
+ OSMO_VALUE_STRING(RAN_PEER_EV_MSG_DOWN_CL),
+ OSMO_VALUE_STRING(RAN_PEER_EV_MSG_DOWN_CO_INITIAL),
+ OSMO_VALUE_STRING(RAN_PEER_EV_MSG_DOWN_CO),
+ OSMO_VALUE_STRING(RAN_PEER_EV_RX_RESET),
+ OSMO_VALUE_STRING(RAN_PEER_EV_RX_RESET_ACK),
+ OSMO_VALUE_STRING(RAN_PEER_EV_CONNECTION_SUCCESS),
+ OSMO_VALUE_STRING(RAN_PEER_EV_CONNECTION_TIMEOUT),
+ {}
+};
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state ran_peer_fsm_states[] = {
+ [RAN_PEER_ST_WAIT_RX_RESET] = {
+ .name = "WAIT_RX_RESET",
+ .action = ran_peer_st_wait_rx_reset,
+ .in_event_mask = 0
+ | S(RAN_PEER_EV_RX_RESET)
+ | S(RAN_PEER_EV_MSG_UP_CO_INITIAL)
+ | S(RAN_PEER_EV_MSG_UP_CO)
+ | S(RAN_PEER_EV_CONNECTION_TIMEOUT)
+ ,
+ .out_state_mask = 0
+ | S(RAN_PEER_ST_WAIT_RX_RESET)
+ | S(RAN_PEER_ST_WAIT_RX_RESET_ACK)
+ | S(RAN_PEER_ST_READY)
+ | S(RAN_PEER_ST_DISCARDING)
+ ,
+ },
+ [RAN_PEER_ST_WAIT_RX_RESET_ACK] = {
+ .name = "WAIT_RX_RESET_ACK",
+ .action = ran_peer_st_wait_rx_reset_ack,
+ .in_event_mask = 0
+ | S(RAN_PEER_EV_RX_RESET)
+ | S(RAN_PEER_EV_RX_RESET_ACK)
+ | S(RAN_PEER_EV_MSG_UP_CO_INITIAL)
+ | S(RAN_PEER_EV_MSG_UP_CO)
+ | S(RAN_PEER_EV_CONNECTION_TIMEOUT)
+ ,
+ .out_state_mask = 0
+ | S(RAN_PEER_ST_WAIT_RX_RESET)
+ | S(RAN_PEER_ST_WAIT_RX_RESET_ACK)
+ | S(RAN_PEER_ST_READY)
+ | S(RAN_PEER_ST_DISCARDING)
+ ,
+ },
+ [RAN_PEER_ST_READY] = {
+ .name = "READY",
+ .action = ran_peer_st_ready,
+ .in_event_mask = 0
+ | S(RAN_PEER_EV_RX_RESET)
+ | S(RAN_PEER_EV_MSG_UP_CO_INITIAL)
+ | S(RAN_PEER_EV_MSG_UP_CO)
+ | S(RAN_PEER_EV_MSG_DOWN_CO_INITIAL)
+ | S(RAN_PEER_EV_MSG_DOWN_CO)
+ | S(RAN_PEER_EV_MSG_DOWN_CL)
+ ,
+ .out_state_mask = 0
+ | S(RAN_PEER_ST_WAIT_RX_RESET)
+ | S(RAN_PEER_ST_WAIT_RX_RESET_ACK)
+ | S(RAN_PEER_ST_READY)
+ | S(RAN_PEER_ST_DISCARDING)
+ ,
+ },
+ [RAN_PEER_ST_DISCARDING] = {
+ .name = "DISCARDING",
+ },
+};
+
+static struct osmo_fsm ran_peer_fsm = {
+ .name = "ran_peer",
+ .states = ran_peer_fsm_states,
+ .num_states = ARRAY_SIZE(ran_peer_fsm_states),
+ .log_subsys = DRR,
+ .event_names = ran_peer_fsm_event_names,
+ .timer_cb = ran_peer_fsm_timer_cb,
+ .cleanup = ran_peer_fsm_cleanup,
+ .allstate_action = ran_peer_allstate_action,
+ .allstate_event_mask = 0
+ | S(RAN_PEER_EV_MSG_UP_CL)
+ ,
+};
+
+int ran_peer_up_l2(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *calling_addr, bool co, uint32_t conn_id,
+ struct msgb *l2)
+{
+ struct ran_peer *ran_peer = NULL;
+ uint32_t event;
+ struct ran_peer_ev_ctx ctx = {
+ .conn_id = conn_id,
+ .msg = l2,
+ };
+
+ if (co) {
+ struct ran_conn *conn;
+ llist_for_each_entry(conn, &sri->ran_conns, entry) {
+ if (conn->sccp_conn_id == conn_id) {
+ ran_peer = conn->ran_peer;
+ ctx.conn = conn;
+ break;
+ }
+ }
+
+ if (ran_peer && calling_addr) {
+ LOG_SCCP_RAN_CO(sri, calling_addr, conn_id, LOGL_ERROR,
+ "Connection-Oriented Initial message for already existing conn_id."
+ " Dropping message.\n");
+ return -EINVAL;
+ }
+
+ if (!ran_peer && !calling_addr) {
+ LOG_SCCP_RAN_CO(sri, calling_addr, conn_id, LOGL_ERROR,
+ "Connection-Oriented non-Initial message for unknown conn_id %u."
+ " Dropping message.\n", conn_id);
+ return -EINVAL;
+ }
+ }
+
+ if (calling_addr) {
+ ran_peer = ran_peer_find_or_create(sri, calling_addr);
+ if (!ran_peer) {
+ LOG_SCCP_RAN_CL(sri, calling_addr, LOGL_ERROR, "Cannot register RAN peer\n");
+ return -EIO;
+ }
+ }
+
+ OSMO_ASSERT(ran_peer && ran_peer->fi);
+
+ if (co)
+ event = calling_addr ? RAN_PEER_EV_MSG_UP_CO_INITIAL : RAN_PEER_EV_MSG_UP_CO;
+ else
+ event = RAN_PEER_EV_MSG_UP_CL;
+
+ return osmo_fsm_inst_dispatch(ran_peer->fi, event, &ctx);
+}
+
+void ran_peer_disconnect(struct sccp_ran_inst *sri, uint32_t conn_id)
+{
+ struct ran_conn *conn;
+ llist_for_each_entry(conn, &sri->ran_conns, entry) {
+ if (conn->sccp_conn_id == conn_id) {
+ ran_conn_discard(conn);
+ return;
+ }
+ }
+}
+
+struct ran_peer *ran_peer_find_by_cell_id(struct sccp_ran_inst *sri, const struct gsm0808_cell_id *cid,
+ bool expecting_single_match)
+{
+ struct ran_peer *rp;
+ struct ran_peer *found = NULL;
+
+ llist_for_each_entry(rp, &sri->ran_peers, entry) {
+ if (cell_id_list_find(&rp->cells_seen, cid, 0, false)) {
+ if (!expecting_single_match)
+ return rp;
+ /* Otherwise continue iterating and log errors for multiple matches... */
+ if (found) {
+ LOG_RAN_PEER(found, LOGL_ERROR, "Cell appears in more than one RAN peer:"
+ " %s also appears in %s\n",
+ gsm0808_cell_id_name(cid), rp->fi->id);
+ } else
+ found = rp;
+ }
+ }
+ return found;
+}
+
+struct ran_peer *ran_peer_find_by_addr(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *addr)
+{
+ struct ran_peer *rp;
+
+ llist_for_each_entry(rp, &sri->ran_peers, entry) {
+ if (!osmo_sccp_addr_ri_cmp(addr, &rp->peer_addr))
+ return rp;
+ }
+ return NULL;
+}
+
+int ran_peers_down_paging(struct sccp_ran_inst *sri, enum CELL_IDENT page_where, struct vlr_subscr *vsub,
+ enum paging_cause cause)
+{
+ struct ran_peer *rp;
+ int ret = 0;
+ struct gsm0808_cell_id page_id;
+ gsm0808_cell_id_from_cgi(&page_id, page_where, &vsub->cgi);
+
+ switch (page_where) {
+ case CELL_IDENT_NO_CELL:
+ LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Asked to page on NO_CELL, wich doesn't make sense.\n");
+ return 0;
+
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
+ LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Don't know how to page on %s\n",
+ gsm0808_cell_id_name(&page_id));
+ return 0;
+
+ default:
+ break;
+ };
+
+ llist_for_each_entry(rp, &sri->ran_peers, entry) {
+ ret += ran_peer_down_paging(rp, &page_id, vsub, cause);
+ }
+
+ if (!ret)
+ LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Paging failed, no RAN peers found for %s\n",
+ gsm0808_cell_id_name(&page_id));
+ return ret;
+}
+
+/* If the given vsub->cgi matches this ran_peer with respect to page_where, page and return 1.
+ * Otherwise return 0. (Return value: number of pagings sent) */
+int ran_peer_down_paging(struct ran_peer *rp, const struct gsm0808_cell_id *page_id, struct vlr_subscr *vsub,
+ enum paging_cause cause)
+{
+ struct msgb *l2;
+
+ if (cell_id_list_find(&rp->cells_seen, page_id, 0, false))
+ goto page_it;
+
+ /* There are also the RAN peers that are configured in the neighbor ident for Handover, but if those aren't
+ * connected, then we can't Page there. */
+
+ return 0;
+
+page_it:
+ LOG_RAN_PEER_CAT(rp, DPAG, LOGL_DEBUG, "Paging for %s on %s\n", vlr_subscr_name(vsub),
+ gsm0808_cell_id_name(page_id));
+ l2 = rp->sri->ran->sccp_ran_ops.make_paging_msg(rp->sri, page_id, vsub->imsi, vsub->tmsi, cause);
+ if (osmo_fsm_inst_dispatch(rp->fi, RAN_PEER_EV_MSG_DOWN_CL, l2)) {
+ /* Not allowed to send messages, the peer is not properly connected yet/anymore */
+ LOG_RAN_PEER_CAT(rp, DPAG, LOGL_ERROR,
+ "Paging for %s matched this RAN peer, but emitting a Paging failed\n",
+ gsm0808_cell_id_name(page_id));
+ return 0;
+ }
+ return 1;
+}
diff --git a/src/libmsc/ran_up_l2.c b/src/libmsc/ran_up_l2.c
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/libmsc/ran_up_l2.c
diff --git a/src/libmsc/rrlp.c b/src/libmsc/rrlp.c
index 6ff30b748..43b2c75dc 100644
--- a/src/libmsc/rrlp.c
+++ b/src/libmsc/rrlp.c
@@ -27,7 +27,7 @@
#include <osmocom/msc/signal.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_subscriber.h>
-#include <osmocom/msc/ran_conn.h>
+#include <osmocom/msc/msc_a.h>
/* RRLP msPositionReq, nsBased,
* Accuracy=60, Method=gps, ResponseTime=2, oneSet */
@@ -59,9 +59,9 @@ const char *msc_rrlp_mode_name(enum rrlp_mode mode)
return get_value_string(rrlp_mode_names, mode);
}
-static int send_rrlp_req(struct ran_conn *conn)
+static int send_rrlp_req(struct msc_a *msc_a)
{
- struct gsm_network *net = conn->network;
+ struct gsm_network *net = msc_a_net(msc_a);
const uint8_t *req;
switch (net->rrlp.mode) {
@@ -79,27 +79,25 @@ static int send_rrlp_req(struct ran_conn *conn)
return 0;
}
- LOGP(DRR, LOGL_INFO, "Sending '%s' RRLP position request\n",
- msc_rrlp_mode_name(net->rrlp.mode));
+ LOG_MSC_A_CAT(msc_a, DRR, LOGL_INFO, "Sending '%s' RRLP position request\n", msc_rrlp_mode_name(net->rrlp.mode));
- return gsm48_send_rr_app_info(conn, 0x00,
- sizeof(ms_based_pos_req), req);
+ return gsm48_send_rr_app_info(msc_a, 0x00, sizeof(ms_based_pos_req), req);
}
static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct vlr_subscr *vsub;
- struct ran_conn *conn;
+ struct msc_a *msc_a;
switch (signal) {
case S_SUBSCR_ATTACHED:
/* A subscriber has attached. */
vsub = signal_data;
- conn = connection_for_subscr(vsub);
- if (!conn)
+ msc_a = msc_a_for_vsub(vsub, true);
+ if (!msc_a)
break;
- send_rrlp_req(conn);
+ send_rrlp_req(msc_a);
break;
}
return 0;
@@ -113,7 +111,7 @@ static int paging_sig_cb(unsigned int subsys, unsigned int signal,
switch (signal) {
case S_PAGING_SUCCEEDED:
/* A subscriber has attached. */
- send_rrlp_req(psig_data->conn);
+ send_rrlp_req(psig_data->msc_a);
break;
case S_PAGING_EXPIRED:
break;
diff --git a/src/libmsc/rtp_stream.c b/src/libmsc/rtp_stream.c
new file mode 100644
index 000000000..6163a981f
--- /dev/null
+++ b/src/libmsc/rtp_stream.c
@@ -0,0 +1,389 @@
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+
+#define LOG_RTPS(rtps, level, fmt, args...) \
+ LOGPFSML(rtps->fi, level, fmt, ##args)
+
+enum rtp_stream_event {
+ RTP_STREAM_EV_CRCX_OK,
+ RTP_STREAM_EV_CRCX_FAIL,
+ RTP_STREAM_EV_MDCX_OK,
+ RTP_STREAM_EV_MDCX_FAIL,
+};
+
+enum rtp_stream_state {
+ RTP_STREAM_ST_UNINITIALIZED,
+ RTP_STREAM_ST_ESTABLISHING,
+ RTP_STREAM_ST_ESTABLISHED,
+ RTP_STREAM_ST_DISCARDING,
+};
+
+static struct osmo_fsm rtp_stream_fsm;
+
+static struct osmo_tdef_state_timeout rtp_stream_fsm_timeouts[32] = {
+ [RTP_STREAM_ST_ESTABLISHING] = { .T = -2 },
+};
+
+#define rtp_stream_state_chg(rtps, state) \
+ osmo_tdef_fsm_inst_state_chg((rtps)->fi, state, rtp_stream_fsm_timeouts, g_mgw_tdefs, 5)
+
+static __attribute__((constructor)) void rtp_stream_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&rtp_stream_fsm) == 0);
+}
+
+void rtp_stream_update_id(struct rtp_stream *rtps)
+{
+ char buf[256];
+ char *p;
+ struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
+ OSMO_STRBUF_PRINTF(sb, "%s", rtps->fi->proc.parent->id);
+ if (rtps->for_trans)
+ OSMO_STRBUF_PRINTF(sb, ":trans-%u", rtps->for_trans->transaction_id);
+ OSMO_STRBUF_PRINTF(sb, ":call-%u", rtps->call_id);
+ OSMO_STRBUF_PRINTF(sb, ":%s", rtp_direction_name(rtps->dir));
+ if (!osmo_mgcpc_ep_ci_id(rtps->ci)) {
+ OSMO_STRBUF_PRINTF(sb, ":no-CI");
+ } else {
+ OSMO_STRBUF_PRINTF(sb, ":CI-%s", osmo_mgcpc_ep_ci_id(rtps->ci));
+ if (!osmo_sockaddr_str_is_set(&rtps->remote))
+ OSMO_STRBUF_PRINTF(sb, ":no-remote-port");
+ else if (!rtps->remote_sent_to_mgw)
+ OSMO_STRBUF_PRINTF(sb, ":remote-port-not-sent");
+ if (!rtps->codec_known)
+ OSMO_STRBUF_PRINTF(sb, ":no-codec");
+ else if (!rtps->codec_sent_to_mgw)
+ OSMO_STRBUF_PRINTF(sb, ":codec-not-sent");
+ }
+ if (osmo_sockaddr_str_is_set(&rtps->local))
+ OSMO_STRBUF_PRINTF(sb, ":local-%s-%u", rtps->local.ip, rtps->local.port);
+ if (osmo_sockaddr_str_is_set(&rtps->remote))
+ OSMO_STRBUF_PRINTF(sb, ":remote-%s-%u", rtps->remote.ip, rtps->remote.port);
+
+ /* Replace any dots in the IP address, dots not allowed as FSM instance name */
+ for (p = buf; *p; p++)
+ if (*p == '.')
+ *p = '-';
+
+ osmo_fsm_inst_update_id_f(rtps->fi, "%s", buf);
+}
+
+/* Allocate RTP stream under a call leg. This is one RTP connection from some remote entity with address and port to a
+ * local RTP address and port. call_id is stored for sending in MGCP transactions and as logging context. for_trans is
+ * optional, merely stored for reference by callers, and appears as log context if not NULL. */
+struct rtp_stream *rtp_stream_alloc(struct call_leg *parent_call_leg, enum rtp_direction dir,
+ uint32_t call_id, struct gsm_trans *for_trans)
+{
+ struct osmo_fsm_inst *fi;
+ struct rtp_stream *rtps;
+
+ fi = osmo_fsm_inst_alloc_child(&rtp_stream_fsm, parent_call_leg->fi, CALL_LEG_EV_RTP_STREAM_GONE);
+ OSMO_ASSERT(fi);
+
+ rtps = talloc(fi, struct rtp_stream);
+ OSMO_ASSERT(rtps);
+ fi->priv = rtps;
+ *rtps = (struct rtp_stream){
+ .fi = fi,
+ .parent_call_leg = parent_call_leg,
+ .call_id = call_id,
+ .for_trans = for_trans,
+ .dir = dir,
+ };
+
+ rtp_stream_update_id(rtps);
+
+ return rtps;
+}
+
+static void check_established(struct rtp_stream *rtps)
+{
+ if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED
+ && osmo_sockaddr_str_is_set(&rtps->local)
+ && osmo_sockaddr_str_is_set(&rtps->remote)
+ && rtps->remote_sent_to_mgw
+ && rtps->codec_known)
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHED);
+}
+
+static void rtp_stream_fsm_establishing_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct rtp_stream *rtps = fi->priv;
+ const struct mgcp_conn_peer *crcx_info;
+ switch (event) {
+ case RTP_STREAM_EV_CRCX_OK:
+ crcx_info = osmo_mgcpc_ep_ci_get_rtp_info(rtps->ci);
+ osmo_sockaddr_str_from_str(&rtps->local, crcx_info->addr, crcx_info->port);
+ rtp_stream_update_id(rtps);
+ osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE, rtps);
+ check_established(rtps);
+
+ if ((!rtps->remote_sent_to_mgw || !rtps->codec_sent_to_mgw)
+ && osmo_sockaddr_str_is_set(&rtps->remote)
+ && rtps->codec_known) {
+ LOG_RTPS(rtps, LOGL_DEBUG,
+ "local ip:port set;%s%s triggering MDCX to send the new settings\n",
+ (!rtps->remote_sent_to_mgw)? " remote ip:port not yet sent," : "",
+ (!rtps->codec_sent_to_mgw)? " codec not yet sent," : "");
+ rtp_stream_do_mdcx(rtps);
+ }
+ return;
+
+ case RTP_STREAM_EV_MDCX_OK:
+ rtp_stream_update_id(rtps);
+ check_established(rtps);
+ return;
+
+ case RTP_STREAM_EV_CRCX_FAIL:
+ case RTP_STREAM_EV_MDCX_FAIL:
+ rtps->remote_sent_to_mgw = false;
+ rtps->codec_sent_to_mgw = false;
+ rtp_stream_update_id(rtps);
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ };
+}
+
+void rtp_stream_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct rtp_stream *rtps = fi->priv;
+ osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ESTABLISHED, rtps);
+}
+
+static int rtp_stream_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct rtp_stream *rtps = fi->priv;
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
+ return 0;
+}
+
+static void rtp_stream_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct rtp_stream *rtps = fi->priv;
+ if (rtps->ci) {
+ osmo_mgcpc_ep_ci_dlcx(rtps->ci);
+ rtps->ci = NULL;
+ }
+}
+
+void rtp_stream_fsm_discarding_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static const struct value_string rtp_stream_fsm_event_names[] = {
+ OSMO_VALUE_STRING(RTP_STREAM_EV_CRCX_OK),
+ OSMO_VALUE_STRING(RTP_STREAM_EV_CRCX_FAIL),
+ OSMO_VALUE_STRING(RTP_STREAM_EV_MDCX_OK),
+ OSMO_VALUE_STRING(RTP_STREAM_EV_MDCX_FAIL),
+ {}
+};
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state rtp_stream_fsm_states[] = {
+ [RTP_STREAM_ST_UNINITIALIZED] = {
+ .name = "UNINITIALIZED",
+ .out_state_mask = 0
+ | S(RTP_STREAM_ST_ESTABLISHING)
+ | S(RTP_STREAM_ST_DISCARDING)
+ ,
+ },
+ [RTP_STREAM_ST_ESTABLISHING] = {
+ .name = "ESTABLISHING",
+ .in_event_mask = 0
+ | S(RTP_STREAM_EV_CRCX_OK)
+ | S(RTP_STREAM_EV_CRCX_FAIL)
+ | S(RTP_STREAM_EV_MDCX_OK)
+ | S(RTP_STREAM_EV_MDCX_FAIL)
+ ,
+ .out_state_mask = 0
+ | S(RTP_STREAM_ST_ESTABLISHED)
+ | S(RTP_STREAM_ST_DISCARDING)
+ ,
+ .action = rtp_stream_fsm_establishing_established,
+ },
+ [RTP_STREAM_ST_ESTABLISHED] = {
+ .name = "ESTABLISHED",
+ .out_state_mask = 0
+ | S(RTP_STREAM_ST_ESTABLISHING)
+ | S(RTP_STREAM_ST_DISCARDING)
+ ,
+ .onenter = rtp_stream_fsm_established_onenter,
+ .action = rtp_stream_fsm_establishing_established,
+ },
+ [RTP_STREAM_ST_DISCARDING] = {
+ .name = "DISCARDING",
+ .onenter = rtp_stream_fsm_discarding_onenter,
+ .out_state_mask = 0
+ | S(RTP_STREAM_ST_DISCARDING)
+ ,
+ },
+};
+
+static struct osmo_fsm rtp_stream_fsm = {
+ .name = "rtp_stream",
+ .states = rtp_stream_fsm_states,
+ .num_states = ARRAY_SIZE(rtp_stream_fsm_states),
+ .log_subsys = DCC,
+ .event_names = rtp_stream_fsm_event_names,
+ .timer_cb = rtp_stream_fsm_timer_cb,
+ .cleanup = rtp_stream_fsm_cleanup,
+};
+
+static int rtp_stream_do_mgcp_verb(struct rtp_stream *rtps, enum mgcp_verb verb, uint32_t ok_event, uint32_t fail_event)
+{
+ struct mgcp_conn_peer verb_info;
+
+ if (!rtps->ci) {
+ LOG_RTPS(rtps, LOGL_ERROR, "Cannot send %s, no endpoint CI allocated\n", osmo_mgcp_verb_name(verb));
+ return -EINVAL;
+ }
+
+ verb_info = (struct mgcp_conn_peer){
+ .call_id = rtps->call_id,
+ .ptime = 20,
+ };
+
+ if (verb == MGCP_VERB_CRCX)
+ verb_info.conn_mode = rtps->crcx_conn_mode;
+
+ if (rtps->codec_known) {
+ verb_info.codecs[0] = rtps->codec;
+ verb_info.codecs_len = 1;
+ rtps->codec_sent_to_mgw = true;
+ }
+ if (osmo_sockaddr_str_is_set(&rtps->remote)) {
+ int rc = osmo_strlcpy(verb_info.addr, rtps->remote.ip, sizeof(verb_info.addr));
+ if (rc <= 0 || rc >= sizeof(verb_info.addr)) {
+ LOG_RTPS(rtps, LOGL_ERROR, "Failure to write IP address to MGCP message (rc=%d)\n", rc);
+ return -ENOSPC;
+ }
+ verb_info.port = rtps->remote.port;
+ rtps->remote_sent_to_mgw = true;
+ }
+
+ osmo_mgcpc_ep_ci_request(rtps->ci, verb, &verb_info, rtps->fi, ok_event, fail_event, NULL);
+ return 0;
+}
+
+int rtp_stream_ensure_ci(struct rtp_stream *rtps, struct osmo_mgcpc_ep *at_endpoint)
+{
+ if (rtps->ci)
+ return rtp_stream_commit(rtps);
+
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
+
+ rtps->ci = osmo_mgcpc_ep_ci_add(at_endpoint, "%s", rtp_direction_name(rtps->dir));
+ if (!rtps->ci)
+ return -ENODEV;
+
+ return rtp_stream_do_mgcp_verb(rtps, MGCP_VERB_CRCX, RTP_STREAM_EV_CRCX_OK, RTP_STREAM_EV_CRCX_FAIL);
+}
+
+int rtp_stream_do_mdcx(struct rtp_stream *rtps)
+{
+ return rtp_stream_do_mgcp_verb(rtps, MGCP_VERB_MDCX, RTP_STREAM_EV_MDCX_OK, RTP_STREAM_EV_MDCX_FAIL);
+}
+
+void rtp_stream_release(struct rtp_stream *rtps)
+{
+ if (!rtps)
+ return;
+
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
+}
+
+/* After setting up a remote RTP address or a new codec, call this to trigger an MDCX.
+ * The MDCX will only trigger if all data needed by an endpoint is available (both RTP address and codec) and if at
+ * least one of them has not yet been sent to the MGW in a previous CRCX or MDCX. */
+int rtp_stream_commit(struct rtp_stream *rtps)
+{
+ if (!rtps->ci) {
+ LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no MGW endpoint CI set up\n");
+ return -1;
+ }
+ if (!osmo_sockaddr_str_is_set(&rtps->remote)) {
+ LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no remote RTP address known\n");
+ return -1;
+ }
+ if (!rtps->codec_known) {
+ LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no codec known\n");
+ return -1;
+ }
+ if (rtps->remote_sent_to_mgw && rtps->codec_sent_to_mgw) {
+ LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: both remote RTP address and codec already set up at MGW\n");
+ return 0;
+ }
+
+ LOG_RTPS(rtps, LOGL_DEBUG, "Committing: Tx MDCX to update the MGW: updating%s%s\n",
+ rtps->remote_sent_to_mgw ? "" : " remote-RTP-IP-port",
+ rtps->codec_sent_to_mgw ? "" : " codec");
+ return rtp_stream_do_mdcx(rtps);
+}
+
+void rtp_stream_set_codec(struct rtp_stream *rtps, enum mgcp_codecs codec)
+{
+ if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED)
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
+ LOG_RTPS(rtps, LOGL_DEBUG, "setting codec to %s\n", osmo_mgcpc_codec_name(codec));
+ rtps->codec = codec;
+ rtps->codec_known = true;
+ rtps->codec_sent_to_mgw = false;
+ rtp_stream_update_id(rtps);
+}
+
+void rtp_stream_set_remote_addr(struct rtp_stream *rtps, const struct osmo_sockaddr_str *r)
+{
+ if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED)
+ rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
+ LOG_RTPS(rtps, LOGL_DEBUG, "setting remote addr to " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(r));
+ rtps->remote = *r;
+ rtps->remote_sent_to_mgw = false;
+ rtp_stream_update_id(rtps);
+}
+
+bool rtp_stream_is_established(struct rtp_stream *rtps)
+{
+ if (!rtps)
+ return false;
+ if (!rtps->fi)
+ return false;
+ if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED)
+ return false;
+ if (!rtps->remote_sent_to_mgw
+ || !rtps->codec_sent_to_mgw)
+ return false;
+ return true;
+}
diff --git a/src/libmsc/sccp_ran.c b/src/libmsc/sccp_ran.c
new file mode 100644
index 000000000..99317b500
--- /dev/null
+++ b/src/libmsc/sccp_ran.c
@@ -0,0 +1,216 @@
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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/logging.h>
+
+#include <osmocom/sccp/sccp_types.h>
+#include <osmocom/sigtran/sccp_sap.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/ran_infra.h>
+
+struct osmo_tdef g_sccp_tdefs[] = {
+ {}
+};
+
+static int sccp_ran_sap_up(struct osmo_prim_hdr *oph, void *_scu);
+
+struct sccp_ran_inst *sccp_ran_init(void *talloc_ctx, struct osmo_sccp_instance *sccp, enum osmo_sccp_ssn ssn,
+ const char *sccp_user_name, struct ran_infra *ran, void *user_data)
+{
+ struct sccp_ran_inst *sri = talloc(talloc_ctx, struct sccp_ran_inst);
+ *sri = (struct sccp_ran_inst){
+ .ran = ran,
+ .sccp = sccp,
+ .user_data = user_data,
+ };
+
+ INIT_LLIST_HEAD(&sri->ran_peers);
+ INIT_LLIST_HEAD(&sri->ran_conns);
+
+ osmo_sccp_local_addr_by_instance(&sri->local_sccp_addr, sccp, ssn);
+ sri->scu = osmo_sccp_user_bind(sccp, sccp_user_name, sccp_ran_sap_up, ssn);
+ osmo_sccp_user_set_priv(sri->scu, sri);
+
+ OSMO_ASSERT(!ran->sri);
+ ran->sri = sri;
+
+ return sri;
+}
+
+static int sccp_ran_sap_up(struct osmo_prim_hdr *oph, void *_scu)
+{
+ struct osmo_sccp_user *scu = _scu;
+ struct sccp_ran_inst *sri = osmo_sccp_user_get_priv(scu);
+ struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph;
+ struct osmo_sccp_addr *my_addr;
+ struct osmo_sccp_addr *peer_addr;
+ uint32_t conn_id;
+ int rc;
+
+ if (!sri->ran || !sri->ran->sccp_ran_ops.up_l2) {
+ LOG_SCCP_RAN_CL(sri, NULL, LOGL_ERROR, "This RAN type is not set up\n");
+ msgb_free(oph->msg);
+ return -1;
+ }
+
+ switch (OSMO_PRIM_HDR(oph)) {
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
+ /* indication of new inbound connection request */
+ conn_id = prim->u.connect.conn_id;
+ my_addr = &prim->u.connect.called_addr;
+ peer_addr = &prim->u.connect.calling_addr;
+ LOG_SCCP_RAN_CO(sri, peer_addr, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
+
+ if (!msgb_l2(oph->msg) || msgb_l2len(oph->msg) == 0) {
+ LOG_SCCP_RAN_CO(sri, peer_addr, conn_id, LOGL_NOTICE, "Received invalid N-CONNECT.ind\n");
+ rc = -1;
+ break;
+ }
+
+ if (osmo_sccp_addr_ri_cmp(&sri->local_sccp_addr, my_addr))
+ LOG_SCCP_RAN_CO(sri, NULL, conn_id, LOGL_INFO,
+ "Called address is %s which is not the locally configured address\n",
+ osmo_sccp_inst_addr_name(sri->sccp, my_addr));
+
+ /* ensure the local SCCP socket is ACTIVE */
+ osmo_sccp_tx_conn_resp(scu, conn_id, my_addr, NULL, 0);
+
+ rc = sri->ran->sccp_ran_ops.up_l2(sri, peer_addr, true, conn_id, oph->msg);
+ if (rc)
+ osmo_sccp_tx_disconn(scu, conn_id, my_addr, SCCP_RETURN_CAUSE_UNQUALIFIED);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
+ /* connection-oriented data received */
+ conn_id = prim->u.data.conn_id;
+ LOG_SCCP_RAN_CO(sri, NULL, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
+
+ rc = sri->ran->sccp_ran_ops.up_l2(sri, NULL, true, conn_id, oph->msg);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
+ /* indication of disconnect */
+ conn_id = prim->u.disconnect.conn_id;
+ LOG_SCCP_RAN_CO(sri, NULL, conn_id, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
+
+ /* If there is no L2 payload in the N-DISCONNECT, no need to dispatch up_l2(). */
+ if (msgb_l2len(oph->msg))
+ rc = sri->ran->sccp_ran_ops.up_l2(sri, NULL, true, conn_id, oph->msg);
+ else
+ rc = 0;
+
+ /* Make sure the ran_conn is dropped. It might seem more optimal to combine the disconnect() into
+ * up_l2(), but since an up_l2() dispatch might already cause the ran_conn to be discarded for other
+ * reasons, a separate disconnect() with a separate conn_id lookup is actually necessary. */
+ sri->ran->sccp_ran_ops.disconnect(sri, conn_id);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
+ /* connection-less data received */
+ my_addr = &prim->u.unitdata.called_addr;
+ peer_addr = &prim->u.unitdata.calling_addr;
+ LOG_SCCP_RAN_CL(sri, peer_addr, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
+
+ if (osmo_sccp_addr_ri_cmp(&sri->local_sccp_addr, my_addr))
+ LOG_SCCP_RAN_CL(sri, NULL, LOGL_INFO,
+ "Called address is %s which is not the locally configured address\n",
+ osmo_sccp_inst_addr_name(sri->sccp, my_addr));
+
+ rc = sri->ran->sccp_ran_ops.up_l2(sri, peer_addr, false, 0, oph->msg);
+ break;
+
+ default:
+ LOG_SCCP_RAN_CL(sri, NULL, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_scu_prim_name(oph));
+ rc = -1;
+ break;
+ }
+
+ msgb_free(oph->msg);
+ return rc;
+}
+
+/* Push some padding if necessary to reach a multiple-of-eight offset to be msgb_push() an osmo_scu_prim that will then
+ * be 8-byte aligned. */
+static void msgb_pad_mod8(struct msgb *msg)
+{
+ uint8_t mod8 = (intptr_t)(msg->data) % 8;
+ if (mod8)
+ msgb_push(msg, mod8);
+}
+
+int sccp_ran_down_l2_co_initial(struct sccp_ran_inst *sri,
+ const struct osmo_sccp_addr *called_addr,
+ uint32_t conn_id, struct msgb *l2)
+{
+ struct osmo_scu_prim *prim;
+
+ l2->l2h = l2->data;
+
+ msgb_pad_mod8(l2);
+ prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim));
+ prim->u.connect = (struct osmo_scu_connect_param){
+ .called_addr = *called_addr,
+ .calling_addr = sri->local_sccp_addr,
+ .sccp_class = 2,
+ //.importance = ?,
+ .conn_id = conn_id,
+ };
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, l2);
+ return osmo_sccp_user_sap_down_nofree(sri->scu, &prim->oph);
+}
+
+int sccp_ran_down_l2_co(struct sccp_ran_inst *sri, uint32_t conn_id, struct msgb *l2)
+{
+ struct osmo_scu_prim *prim;
+
+ l2->l2h = l2->data;
+
+ msgb_pad_mod8(l2);
+ prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim));
+ prim->u.data.conn_id = conn_id;
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, l2);
+ return osmo_sccp_user_sap_down_nofree(sri->scu, &prim->oph);
+}
+
+int sccp_ran_down_l2_cl(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *called_addr, struct msgb *l2)
+{
+ struct osmo_scu_prim *prim;
+
+ l2->l2h = l2->data;
+
+ msgb_pad_mod8(l2);
+ prim = (struct osmo_scu_prim *) msgb_push(l2, sizeof(*prim));
+ prim->u.unitdata = (struct osmo_scu_unitdata_param){
+ .called_addr = *called_addr,
+ .calling_addr = sri->local_sccp_addr,
+ };
+ osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_REQUEST, l2);
+ return osmo_sccp_user_sap_down_nofree(sri->scu, &prim->oph);
+}
+
+int sccp_ran_disconnect(struct sccp_ran_inst *sri, uint32_t conn_id, uint32_t cause)
+{
+ return osmo_sccp_tx_disconn(sri->scu, conn_id, NULL, cause);
+}
diff --git a/src/libmsc/sgs_iface.c b/src/libmsc/sgs_iface.c
index 8b4b61d30..d83a730ef 100644
--- a/src/libmsc/sgs_iface.c
+++ b/src/libmsc/sgs_iface.c
@@ -36,13 +36,14 @@
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/vlr_sgs.h>
#include <osmocom/msc/gsm_data.h>
-#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/gsm_04_08.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_i.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/sgs_iface.h>
#include <osmocom/msc/sgs_server.h>
-#include <osmocom/msc/msc_ifaces.h>
#include <osmocom/gsm/protocol/gsm_29_118.h>
#include <osmocom/gsm/apn.h>
@@ -88,44 +89,43 @@ enum sgs_vlr_reset_fsm_event {
***********************************************************************/
/* Allocate a new subscriber connection */
-static struct ran_conn *subscr_conn_allocate_sgs(struct sgs_connection *sgc, struct vlr_subscr *vsub, bool mt)
+static struct msc_a *subscr_conn_allocate_sgs(struct sgs_connection *sgc, struct vlr_subscr *vsub, bool mt)
{
- struct ran_conn *conn;
+ struct msub *msub;
+ struct msc_a *msc_a;
- conn = ran_conn_alloc(gsm_network, OSMO_RAT_EUTRAN_SGS, vsub->sgs.lai.lac);
- if (!conn) {
- LOGSGC_VSUB(sgc, vlr_subscr_name(vsub), LOGL_ERROR, "Connection allocation failed\n");
- return NULL;
- }
+ msub = msub_alloc(gsm_network);
+ msc_a = msc_a_alloc(msub,
+ &msc_ran_infra[OSMO_RAT_EUTRAN_SGS]);
+ msc_a->complete_layer3_type = mt ? COMPLETE_LAYER3_PAGING_RESP : COMPLETE_LAYER3_CM_SERVICE_REQ;
+ msub_set_vsub(msub, vsub);
- vlr_subscr_get(vsub, VSUB_USE_CONN);
- conn->vsub = vsub;
- conn->vsub->cs.attached_via_ran = conn->via_ran;
+ if (mt)
+ msc_a_get(msc_a, MSC_A_USE_PAGING_RESPONSE);
/* Accept the connection immediately, since the UE is already
* authenticated by the MME no authentication is required. */
- conn->complete_layer3_type = mt ? COMPLETE_LAYER3_PAGING_RESP : COMPLETE_LAYER3_CM_SERVICE_REQ;
- ran_conn_update_id(conn);
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_COMPLETE_LAYER_3, NULL);
- osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_ACCEPTED, NULL);
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_COMPLETE_LAYER_3_OK, NULL);
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_AUTHENTICATED, NULL);
- LOG_RAN_CONN(conn, LOGL_DEBUG, "RAN connection successfully allocated!\n");
- return conn;
+ return msc_a;
}
/* Check if there are connections associated with a given subscriber. If yes,
* make sure that those connections are tossed. */
static void subscr_conn_toss(struct vlr_subscr *vsub)
{
- struct ran_conn *conn;
+ struct msub *msub;
- conn = connection_for_subscr(vsub);
- if (!conn)
+ msub = msub_for_vsub(vsub);
+ if (!msub)
return;
- LOG_RAN_CONN(conn, LOGL_DEBUG, "RAN connection tossed because of unexpected RAN change!\n");
+ LOG_MSUB(msub, LOGL_ERROR, "Force releasing previous subscriber connection: an SGs connection for this"
+ " subscriber is being initiated\n");
- ran_conn_mo_close(conn, GSM48_REJECT_CONGESTION);
+ msc_a_release_mo(msub_msc_a(msub), GSM48_REJECT_CONGESTION);
+ /* TODO: is this strong enough? After this, it should be completely disassociated with this subscriber. */
}
struct sgs_mme_ctx *sgs_mme_by_fqdn(struct sgs_state *sgs, const char *mme_fqdn)
@@ -426,6 +426,25 @@ static void sgs_tx_mm_info_cb(struct vlr_subscr *vsub)
msgb_free(msg_mm_info);
}
+enum sgsap_service_ind sgs_serv_ind_from_paging_cause(enum paging_cause cause)
+{
+ switch (cause) {
+ case PAGING_CAUSE_CALL_CONVERSATIONAL:
+ case PAGING_CAUSE_CALL_STREAMING:
+ case PAGING_CAUSE_CALL_INTERACTIVE:
+ case PAGING_CAUSE_CALL_BACKGROUND:
+ return SGSAP_SERV_IND_CS_CALL;
+
+ case PAGING_CAUSE_UNSPECIFIED:
+ case PAGING_CAUSE_SIGNALLING_LOW_PRIO:
+ case PAGING_CAUSE_SIGNALLING_HIGH_PRIO:
+ return SGSAP_SERV_IND_SMS;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
/*! Page UE through SGs interface
* \param[in] vsub subscriber context
* \param[in] serv_ind service indicator (sms or voide)
@@ -436,13 +455,20 @@ int sgs_iface_tx_paging(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind
struct gsm29118_paging_req paging_params;
struct sgs_mme_ctx *mme;
+ LOGP(DMSC, LOGL_NOTICE, "XXXXXXXXXX state == %d conf_by_radio_contact_ind == %d\n",
+ vsub->sgs_fsm->state, vsub->conf_by_radio_contact_ind);
+
/* See also: 3GPP TS 29.118, chapter 5.1.2.2 Paging Initiation */
- if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && vsub->conf_by_radio_contact_ind == true)
+ if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && vsub->conf_by_radio_contact_ind == true) {
+ LOGPFSMSL(vsub->sgs_fsm, DPAG, LOGL_ERROR, "Will not Page (conf_by_radio_contact_ind == true)\n");
return -EINVAL;
+ }
mme = sgs_mme_ctx_by_vsub(vsub, SGSAP_MSGT_PAGING_REQ);
- if (!mme)
+ if (!mme) {
+ LOGPFSMSL(vsub->sgs_fsm, DPAG, LOGL_ERROR, "Will not Page (no MME)\n");
return -EINVAL;
+ }
/* Check if there is still a paging in progress for this subscriber,
* if yes, don't initiate another paging request. */
@@ -690,7 +716,7 @@ static int sgs_rx_pag_rej(struct sgs_connection *sgc, struct msgb *msg, const st
vlr_sgs_pag_rej(gsm_network->vlr, imsi, cause);
/* Stop all paging activity */
- subscr_paging_cancel(vsub, GSM_PAGING_EXPIRED);
+ paging_expired(vsub);
/* Depending on the cause code some action is required */
if (cause == SGSAP_SGS_CAUSE_MT_CSFB_REJ_USER) {
@@ -735,7 +761,7 @@ static int sgs_rx_service_req(struct sgs_connection *sgc, struct msgb *msg, cons
{
enum sgsap_service_ind serv_ind;
const uint8_t *serv_ind_ie;
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct vlr_subscr *vsub;
/* Note: While in other RAN concepts a service request is used to
@@ -788,13 +814,13 @@ static int sgs_rx_service_req(struct sgs_connection *sgc, struct msgb *msg, cons
}
/* Allocate subscriber connection */
- conn = subscr_conn_allocate_sgs(sgc, vsub, true);
- if (!conn) {
+ msc_a = subscr_conn_allocate_sgs(sgc, vsub, true);
+ if (!msc_a) {
vlr_subscr_put(vsub, __func__);
return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_MSG_INCOMP_STATE, msg, -1);
}
- /* The conn has added a get() for the vsub, balance above vlr_subscr_find_by_imsi() */
+ /* The msub has added a get() for the vsub, balance above vlr_subscr_find_by_imsi() */
vlr_subscr_put(vsub, __func__);
return 0;
}
@@ -802,8 +828,7 @@ static int sgs_rx_service_req(struct sgs_connection *sgc, struct msgb *msg, cons
/* SGsAP-UPLINK-UNITDATA 3GPP TS 29.118, chapter 8.22 */
static int sgs_rx_ul_ud(struct sgs_connection *sgc, struct msgb *msg, const struct tlv_parsed *tp, char *imsi)
{
- struct dtap_header *dtap;
- struct ran_conn *conn;
+ struct msc_a *msc_a;
const uint8_t *nas_msg_container_ie;
struct vlr_subscr *vsub;
@@ -816,39 +841,31 @@ static int sgs_rx_ul_ud(struct sgs_connection *sgc, struct msgb *msg, const stru
OSMO_ASSERT(vsub);
/* Try to find existing connection (MT) or allocate a new one (MO) */
- conn = connection_for_subscr(vsub);
- if (!conn) {
- conn = subscr_conn_allocate_sgs(sgc, vsub, false);
- } else {
- if (conn->via_ran != OSMO_RAT_EUTRAN_SGS) {
- LOGSGC(sgc, LOGL_ERROR,
- "Receiving uplink unit-data for non-sgs connection -- discarding message!\n");
- msgb_free(msg);
- return 0;
- }
- }
+ msc_a = msc_a_for_vsub(vsub, true);
+ if (!msc_a)
+ msc_a = subscr_conn_allocate_sgs(sgc, vsub, false);
/* Balance above vlr_subscr_find_by_imsi() */
vlr_subscr_put(vsub, __func__);
/* If we do not find an existing connection and allocating a new one
* faild, give up and return status. */
- if (!conn) {
+ if (!msc_a)
return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_MSG_INCOMP_STATE, msg, 0);
+
+ if (msc_a->c.ran->type != OSMO_RAT_EUTRAN_SGS) {
+ LOGSGC(sgc, LOGL_ERROR,
+ "Receiving uplink unit-data for non-sgs connection -- discarding message!\n");
+ return -EINVAL;
}
nas_msg_container_ie = TLVP_VAL_MINLEN(tp, SGSAP_IE_NAS_MSG_CONTAINER, 1);
- if (!nas_msg_container_ie) {
+ if (!nas_msg_container_ie)
return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_MISSING_MAND_IE, msg, SGSAP_IE_NAS_MSG_CONTAINER);
- }
/* ran_conn_dtap expects the dtap payload in l3h */
- dtap = (struct dtap_header *)nas_msg_container_ie;
- msg->l3h = (uint8_t *) nas_msg_container_ie;
- OMSC_LINKID_CB(msg) = dtap->link_id;
-
- /* Forward dtap payload into the msc */
- ran_conn_dtap(conn, msg);
+ msg->l3h = (uint8_t *)nas_msg_container_ie;
+ msc_a_up_l3(msc_a, msg);
return 0;
}
@@ -1180,22 +1197,14 @@ static struct osmo_fsm sgs_vlr_reset_fsm = {
/*! Send unit-data through SGs interface (see msc_ifaces.c)
* \param[in] msg layer 3 message to send.
* \returns 0 in case of success, -EINVAL in case of error. */
-int sgs_iface_tx_dtap_ud(struct msgb *msg)
+int sgs_iface_tx_dtap_ud(struct msc_a *msc_a, struct msgb *msg)
{
- struct ran_conn *conn;
- struct vlr_subscr *vsub;
struct msgb *msg_sgs;
struct sgs_mme_ctx *mme;
int rc = -EINVAL;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
- /* This function expects a pointer to the related gsm subscriber
- * connection (conn) in msg->dst. Also conn->vsub must point to
- * the related subscriber */
-
- OSMO_ASSERT(msg->dst);
- conn = msg->dst;
- OSMO_ASSERT(conn->vsub);
- vsub = conn->vsub;
+ OSMO_ASSERT(vsub);
mme = sgs_mme_ctx_by_vsub(vsub, SGSAP_MSGT_DL_UD);
if (!mme)
@@ -1204,8 +1213,8 @@ int sgs_iface_tx_dtap_ud(struct msgb *msg)
/* Make sure the subscriber has a valid SGs association, otherwise
* don't let unit-data through. */
if (vsub->sgs_fsm->state != SGS_UE_ST_ASSOCIATED) {
- LOG_RAN_CONN(conn, LOGL_NOTICE, "Tx %s subscriber not SGs-associated, dropping\n",
- sgsap_msg_type_name(SGSAP_MSGT_DL_UD));
+ LOG_MSC_A(msc_a, LOGL_NOTICE, "Cannot Tx %s: subscriber not SGs-associated\n",
+ sgsap_msg_type_name(SGSAP_MSGT_DL_UD));
goto error;
}
@@ -1218,21 +1227,12 @@ error:
return rc;
}
-/*! Send a relase message through SGs interface (see msc_ifaces.c)
- * \param[in] msg layer 3 message to send.
- * \returns 0 in case of success, -EINVAL in case of error. */
-void sgs_iface_tx_release(struct ran_conn *conn)
+void sgs_iface_tx_release(struct vlr_subscr *vsub)
{
struct msgb *msg_sgs;
- struct vlr_subscr *vsub;
struct sgs_mme_ctx *mme;
- /*! Use this function to release an SGs connection normally
- * (cause code is 0). This function also automatically causes
- * the VLR subscriber usage to be balanced. */
-
- OSMO_ASSERT(conn->vsub);
- vsub = conn->vsub;
+ OSMO_ASSERT(vsub);
mme = sgs_mme_ctx_by_vsub(vsub, SGSAP_MSGT_DL_UD);
if (!mme)
diff --git a/src/libmsc/sgs_server.c b/src/libmsc/sgs_server.c
index 56f1548cb..28c42cbb5 100644
--- a/src/libmsc/sgs_server.c
+++ b/src/libmsc/sgs_server.c
@@ -18,6 +18,8 @@
*
*/
+#include <errno.h>
+
#include <osmocom/msc/sgs_iface.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/sgs_server.h>
diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c
index 14974f338..3b95a901f 100644
--- a/src/libmsc/silent_call.c
+++ b/src/libmsc/silent_call.c
@@ -32,115 +32,59 @@
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsm_subscriber.h>
#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/paging.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/silent_call.h>
#include <osmocom/sigtran/sccp_helpers.h>
-struct silent_call_data {
- struct gsm0808_channel_type ct;
-
- char traffic_ip[INET_ADDRSTRLEN];
- uint16_t traffic_port;
-
- void *data;
-
- struct osmo_timer_list timer;
- struct ran_conn *conn;
-};
-
-static void timer_cb(void *data)
-{
- struct silent_call_data *scd = (struct silent_call_data *)data;
- ran_conn_communicating(scd->conn);
- talloc_free(scd);
-}
-
/* paging of the requested subscriber has completed */
-static int paging_cb_silent(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *_conn, void *_data)
+void paging_cb_silent(struct msc_a *msc_a, struct gsm_trans *trans)
{
- struct silent_call_data *scd = (struct silent_call_data *)_data;
- struct ran_conn *conn = _conn;
- struct scall_signal_data sigdata;
- struct msgb *msg_ass;
- int rc = 0;
- int i;
-
- if (hooknum != GSM_HOOK_RR_PAGING)
- return -EINVAL;
-
- DEBUGP(DLSMS, "paging_cb_silent: ");
-
- sigdata.conn = conn;
- sigdata.data = scd->data;
+ struct scall_signal_data sigdata = {
+ .msc_a = msc_a,
+ .vty = trans->silent_call.from_vty,
+ };
+ struct ran_msg assignment;
+
+ if (!msc_a) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Silent call: MS not responding to Paging\n");
+ osmo_signal_dispatch(SS_SCALL, S_SCALL_FAILED, &sigdata);
+ trans_free(trans);
+ return;
+ }
- switch (event) {
- case GSM_PAGING_SUCCEEDED:
-#if BEFORE_MSCSPLIT
- /* Re-enable this log output once we can obtain this information via
- * A-interface, see OS#2391. */
- DEBUGPC(DLSMS, "success, using Timeslot %u on ARFCN %u\n",
- conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
-#endif
- conn->silent_call = 1;
-
- /* Increment lchan reference count and mark as active*/
- ran_conn_get(conn, RAN_CONN_USE_SILENT_CALL);
-
- /* Schedule a timer to mark it as active */
- /* This is a hack we we can't call ran_conn_communicating
- * from here because we're in the call back context of
- * a RAN FSM event but before it actually changes its own
- * state and it's not ready to accept this.
- * Of all alternatives considered, making the call in an
- * 'immediate timer' is the least disruptive and least ugly
- * way to do it I could find.
- */
- scd->conn = conn;
- osmo_timer_setup(&scd->timer, timer_cb, scd);
- osmo_timer_schedule(&scd->timer, 0, 0);
-
- /* Manually craft an assignement message with requested mode */
- if (scd->ct.ch_indctr == GSM0808_CHAN_SPEECH) {
- struct gsm0808_speech_codec_list scl;
- union {
- struct sockaddr_storage st;
- struct sockaddr_in in;
- } rtp_addr;
-
- memset(&rtp_addr, 0, sizeof(rtp_addr));
- rtp_addr.in.sin_family = AF_INET;
- rtp_addr.in.sin_port = osmo_htons(scd->traffic_port);
- rtp_addr.in.sin_addr.s_addr = inet_addr(scd->traffic_ip);
-
- for (i = 0; i < scd->ct.perm_spch_len; i++)
- gsm0808_speech_codec_from_chan_type(&scl.codec[i], scd->ct.perm_spch[i]);
- scl.len = scd->ct.perm_spch_len;
-
- msg_ass = gsm0808_create_ass(&scd->ct, NULL, &rtp_addr.st, &scl, NULL);
- } else {
- msg_ass = gsm0808_create_ass(&scd->ct, NULL, NULL, NULL, NULL);
- }
-
- /* Send assignement message, hoping it will work */
- osmo_sccp_tx_data_msg(conn->a.scu, conn->a.conn_id, msg_ass);
-
- /* Signal completion */
+ LOG_MSC_A(msc_a, LOGL_INFO, "Silent call: MS responding to Paging\n");
+
+ trans->msc_a = msc_a;
+ msc_a_get(msc_a, MSC_A_USE_SILENT_CALL);
+
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans);
+
+ assignment = (struct ran_msg){
+ .msg_type = RAN_MSG_ASSIGNMENT_COMMAND,
+ .assignment_command = {
+ .channel_type = &trans->silent_call.ct,
+ .cn_rtp = &trans->silent_call.rtp_cn,
+ },
+ };
+ if (msc_a_ran_down(msc_a, MSC_ROLE_I, &assignment)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Silent call failed\n");
+ osmo_signal_dispatch(SS_SCALL, S_SCALL_FAILED, &sigdata);
+ trans_free(trans);
+ } else {
osmo_signal_dispatch(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
- return 0;
-
- case GSM_PAGING_EXPIRED:
- case GSM_PAGING_BUSY:
- DEBUGP(DLSMS, "expired\n");
- osmo_signal_dispatch(SS_SCALL, S_SCALL_EXPIRED, &sigdata);
- break;
- default:
- rc = -EINVAL;
- break;
}
+}
- talloc_free(scd);
-
- return rc;
+void trans_silent_call_free(struct gsm_trans *trans)
+{
+ struct scall_signal_data sigdata = {
+ .msc_a = trans->msc_a,
+ .vty = trans->silent_call.from_vty,
+ };
+ osmo_signal_dispatch(SS_SCALL, S_SCALL_DETACHED, &sigdata);
}
#if 0
@@ -193,27 +137,20 @@ int silent_call_reroute(struct ran_conn *conn, struct msgb *msg)
int gsm_silent_call_start(struct vlr_subscr *vsub,
const struct gsm0808_channel_type *ct,
const char *traffic_dst_ip, uint16_t traffic_dst_port,
- void *data)
+ struct vty *vty)
{
- struct subscr_request *req;
- struct silent_call_data *scd;
-
- scd = talloc_zero(vsub, struct silent_call_data);
-
- memcpy(&scd->ct, ct, sizeof(scd->ct));
+ struct gsm_network *net = vsub->vlr->user_ctx;
+ struct gsm_trans *trans = trans_alloc(net, vsub, TRANS_SILENT_CALL, 0, 0);
+ trans->silent_call.ct = *ct;
if (traffic_dst_ip) {
- osmo_strlcpy(scd->traffic_ip, traffic_dst_ip, sizeof(scd->traffic_ip));
- scd->traffic_port = traffic_dst_port;
+ osmo_sockaddr_str_from_str(&trans->silent_call.rtp_cn, traffic_dst_ip, traffic_dst_port);
}
+ trans->silent_call.from_vty = vty;
- scd->data = data;
-
- req = subscr_request_conn(vsub, paging_cb_silent, scd,
- "establish silent call",
- SGSAP_SERV_IND_CS_CALL);
- if (!req) {
- talloc_free(scd);
+ if (!paging_request_start(vsub, PAGING_CAUSE_CALL_BACKGROUND, paging_cb_silent, trans,
+ "establish silent call")) {
+ trans_free(trans);
return -ENODEV;
}
@@ -223,31 +160,21 @@ int gsm_silent_call_start(struct vlr_subscr *vsub,
/* end a silent call with a given subscriber */
int gsm_silent_call_stop(struct vlr_subscr *vsub)
{
- struct ran_conn *conn;
-
- conn = connection_for_subscr(vsub);
- if (!conn) {
+ struct msc_a *msc_a = msc_a_for_vsub(vsub, true);
+ struct gsm_trans *trans;
+ if (!msc_a) {
LOGP(DMM, LOGL_ERROR, "%s: Cannot stop silent call, no connection for subscriber\n",
vlr_subscr_name(vsub));
return -ENODEV;
}
/* did we actually establish a silent call for this guy? */
- if (!conn->silent_call) {
- LOGP(DMM, LOGL_ERROR, "%s: Cannot stop silent call, subscriber has no active silent call\n",
- vlr_subscr_name(vsub));
+ trans = trans_find_by_type(msc_a, TRANS_SILENT_CALL);
+ if (!trans) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot stop silent call, subscriber has no active silent call\n");
return -ENOENT;
}
-#if BEFORE_MSCSPLIT
- /* Re-enable this log output once we can obtain this information via
- * A-interface, see OS#2391. */
- DEBUGPC(DLSMS, "Stopping silent call using Timeslot %u on ARFCN %u\n",
- conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
-#endif
-
- conn->silent_call = 0;
- ran_conn_put(conn, RAN_CONN_USE_SILENT_CALL);
-
+ trans_free(trans);
return 0;
}
diff --git a/src/libmsc/smpp_openbsc.c b/src/libmsc/smpp_openbsc.c
index 151c788b2..e6eb0102a 100644
--- a/src/libmsc/smpp_openbsc.c
+++ b/src/libmsc/smpp_openbsc.c
@@ -45,6 +45,7 @@
#include <osmocom/msc/transaction.h>
#include <osmocom/msc/gsm_subscriber.h>
#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msc_a.h>
#include "smpp_smsc.h"
@@ -556,22 +557,21 @@ void smpp_cmd_flush_pending(struct osmo_esme *esme)
void smpp_cmd_ack(struct osmo_smpp_cmd *cmd)
{
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct gsm_trans *trans;
if (cmd->is_report)
goto out;
- conn = connection_for_subscr(cmd->vsub);
- if (!conn) {
- LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
+ msc_a = msc_a_for_vsub(cmd->vsub, true);
+ if (!msc_a) {
+ LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber %s\n", vlr_subscr_name(cmd->vsub));
goto out;
}
- trans = trans_find_by_id(conn, GSM48_PDISC_SMS, cmd->gsm411_trans_id);
+ trans = trans_find_by_id(msc_a, TRANS_SMS, cmd->gsm411_trans_id);
if (!trans) {
- LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
- cmd->gsm411_trans_id);
+ LOG_MSC_A_CAT(msc_a, DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n", cmd->gsm411_trans_id);
goto out;
}
@@ -582,23 +582,23 @@ out:
void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status)
{
- struct ran_conn *conn;
+ struct msc_a *msc_a;
struct gsm_trans *trans;
int gsm411_cause;
if (cmd->is_report)
goto out;
- conn = connection_for_subscr(cmd->vsub);
- if (!conn) {
- LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
+ msc_a = msc_a_for_vsub(cmd->vsub, true);
+ if (!msc_a) {
+ LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber %s\n", vlr_subscr_name(cmd->vsub));
goto out;
}
- trans = trans_find_by_id(conn, GSM48_PDISC_SMS, cmd->gsm411_trans_id);
+ trans = trans_find_by_id(msc_a, TRANS_SMS, cmd->gsm411_trans_id);
if (!trans) {
- LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
- cmd->gsm411_trans_id);
+ LOG_MSC_A_CAT(msc_a, DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
+ cmd->gsm411_trans_id);
goto out;
}
@@ -657,11 +657,12 @@ struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme,
}
static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
- struct ran_conn *conn)
+ struct msc_a *msc_a)
{
struct deliver_sm_t deliver;
int mode, ret;
uint8_t dcs;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
memset(&deliver, 0, sizeof(deliver));
deliver.command_length = 0;
@@ -674,13 +675,13 @@ static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
deliver.source_addr_npi = NPI_Land_Mobile_E212;
snprintf((char *)deliver.source_addr,
sizeof(deliver.source_addr), "%s",
- conn->vsub->imsi);
+ vsub->imsi);
} else {
deliver.source_addr_ton = TON_Network_Specific;
deliver.source_addr_npi = NPI_ISDN_E163_E164;
snprintf((char *)deliver.source_addr,
sizeof(deliver.source_addr), "%s",
- conn->vsub->msisdn);
+ vsub->msisdn);
}
deliver.dest_addr_ton = sms->dst.ton;
@@ -746,19 +747,18 @@ static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
if (ret < 0)
return ret;
- return smpp_cmd_enqueue(esme, conn->vsub, sms,
+ return smpp_cmd_enqueue(esme, vsub, sms,
deliver.sequence_number);
}
static struct smsc *g_smsc;
-int smpp_route_smpp_first(struct gsm_sms *sms, struct ran_conn *conn)
+bool smpp_route_smpp_first()
{
- return g_smsc->smpp_first;
+ return (bool)(g_smsc->smpp_first);
}
-int smpp_try_deliver(struct gsm_sms *sms,
- struct ran_conn *conn)
+int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a)
{
struct osmo_esme *esme;
struct osmo_smpp_addr dst;
@@ -771,7 +771,7 @@ int smpp_try_deliver(struct gsm_sms *sms,
rc = smpp_route(g_smsc, &dst, &esme);
if (!rc)
- rc = deliver_to_esme(esme, sms, conn);
+ rc = deliver_to_esme(esme, sms, msc_a);
return rc;
}
diff --git a/src/libmsc/smpp_smsc.h b/src/libmsc/smpp_smsc.h
index dc7b7c1ad..1c9eae68e 100644
--- a/src/libmsc/smpp_smsc.h
+++ b/src/libmsc/smpp_smsc.h
@@ -19,6 +19,8 @@
#define MODE_7BIT 7
#define MODE_8BIT 8
+struct msc_a;
+
enum esme_read_state {
READ_ST_IN_LEN = 0,
READ_ST_IN_MSG = 1,
@@ -161,8 +163,6 @@ int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode);
struct gsm_sms;
struct ran_conn;
-int smpp_route_smpp_first(struct gsm_sms *sms,
- struct ran_conn *conn);
-int smpp_try_deliver(struct gsm_sms *sms,
- struct ran_conn *conn);
+bool smpp_route_smpp_first();
+int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a);
#endif
diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c
index a56814d68..2c380b294 100644
--- a/src/libmsc/sms_queue.c
+++ b/src/libmsc/sms_queue.c
@@ -50,6 +50,7 @@ struct gsm_sms_pending {
struct llist_head entry;
struct vlr_subscr *vsub;
+ struct msc_a *msc_a;
unsigned long long sms_id;
int failed_attempts;
int resend;
@@ -507,23 +508,12 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal,
* subscriber. If we have some kind of other transmit error we
* should flag the SMS as bad.
*/
- switch (sig_sms->paging_result) {
- case 0:
+ if (sig_sms->paging_result) {
/* BAD SMS? */
db_sms_inc_deliver_attempts(sig_sms->sms);
sms_pending_failed(pending, 0);
- break;
- case GSM_PAGING_EXPIRED:
+ } else {
sms_pending_failed(pending, 1);
- break;
- case GSM_PAGING_BUSY:
- network->sms_queue->pending -= 1;
- sms_pending_free(pending);
- sms_queue_trigger(network->sms_queue);
- break;
- default:
- LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n",
- sig_sms->paging_result);
}
break;
default:
diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c
index 665ad46b2..b909cd89b 100644
--- a/src/libmsc/transaction.c
+++ b/src/libmsc/transaction.c
@@ -25,6 +25,10 @@
#include <osmocom/core/talloc.h>
#include <osmocom/msc/gsm_04_08.h>
#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/paging.h>
+#include <osmocom/msc/silent_call.h>
void *tall_trans_ctx;
@@ -32,22 +36,35 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans);
void _gsm411_sms_trans_free(struct gsm_trans *trans);
void _gsm911_nc_ss_trans_free(struct gsm_trans *trans);
+struct gsm_trans *trans_find_by_type(const struct msc_a *msc_a, enum trans_type type)
+{
+ struct gsm_trans *trans;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+
+ llist_for_each_entry(trans, &net->trans_list, entry) {
+ if (trans->vsub == vsub && trans->type == type)
+ return trans;
+ }
+ return NULL;
+}
+
/*! Find a transaction in connection for given protocol + transaction ID
* \param[in] conn Connection in which we want to find transaction
* \param[in] proto Protocol of transaction
* \param[in] trans_id Transaction ID of transaction
* \returns Matching transaction, if any
*/
-struct gsm_trans *trans_find_by_id(const struct ran_conn *conn,
- uint8_t proto, uint8_t trans_id)
+struct gsm_trans *trans_find_by_id(const struct msc_a *msc_a,
+ enum trans_type type, uint8_t trans_id)
{
struct gsm_trans *trans;
- struct gsm_network *net = conn->network;
- struct vlr_subscr *vsub = conn->vsub;
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
llist_for_each_entry(trans, &net->trans_list, entry) {
if (trans->vsub == vsub &&
- trans->protocol == proto &&
+ trans->type == type &&
trans->transaction_id == trans_id)
return trans;
}
@@ -85,7 +102,7 @@ struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net,
llist_for_each_entry(trans, &net->trans_list, entry) {
if (trans->vsub == vsub &&
- trans->protocol == GSM48_PDISC_SMS &&
+ trans->type == TRANS_SMS &&
trans->sms.sm_rp_mr == sm_rp_mr)
return trans;
}
@@ -93,9 +110,9 @@ struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net,
return NULL;
}
-static const char *trans_vsub_use(uint8_t proto)
+static const char *trans_vsub_use(enum trans_type type)
{
- return get_value_string_or_null(gsm48_pdisc_names, proto) ? : "trans-proto-unknown";
+ return get_value_string_or_null(trans_type_names, type) ? : "trans-type-unknown";
}
/*! Allocate a new transaction and add it to network list
@@ -107,7 +124,7 @@ static const char *trans_vsub_use(uint8_t proto)
*/
struct gsm_trans *trans_alloc(struct gsm_network *net,
struct vlr_subscr *vsub,
- uint8_t protocol, uint8_t trans_id,
+ enum trans_type type, uint8_t trans_id,
uint32_t callref)
{
struct gsm_trans *trans = NULL; /* (NULL for LOG_TRANS() before allocation) */
@@ -118,17 +135,18 @@ struct gsm_trans *trans_alloc(struct gsm_network *net,
return NULL;
}
- trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
+ trans = talloc(tall_trans_ctx, struct gsm_trans);
if (!trans)
return NULL;
- vlr_subscr_get(vsub, trans_vsub_use(protocol));
- trans->vsub = vsub;
- trans->protocol = protocol;
- trans->transaction_id = trans_id;
- trans->callref = callref;
-
- trans->net = net;
+ *trans = (struct gsm_trans){
+ .vsub = vsub,
+ .type = type,
+ .transaction_id = trans_id,
+ .callref = callref,
+ .net = net,
+ };
+ vlr_subscr_get(vsub, trans_vsub_use(type));
llist_add_tail(&trans->entry, &net->trans_list);
LOG_TRANS(trans, LOGL_DEBUG, "New transaction\n");
@@ -140,46 +158,51 @@ struct gsm_trans *trans_alloc(struct gsm_network *net,
*/
void trans_free(struct gsm_trans *trans)
{
- enum ran_conn_use conn_usage_token;
- struct ran_conn *conn;
+ const char *usage_token;
+ struct msc_a *msc_a;
LOG_TRANS(trans, LOGL_DEBUG, "Freeing transaction\n");
- switch (trans->protocol) {
- case GSM48_PDISC_CC:
+ switch (trans->type) {
+ case TRANS_CC:
_gsm48_cc_trans_free(trans);
- conn_usage_token = RAN_CONN_USE_TRANS_CC;
+ usage_token = MSC_A_USE_CC;
break;
- case GSM48_PDISC_SMS:
+ case TRANS_SMS:
_gsm411_sms_trans_free(trans);
- conn_usage_token = RAN_CONN_USE_TRANS_SMS;
+ usage_token = MSC_A_USE_SMS;
break;
- case GSM48_PDISC_NC_SS:
+ case TRANS_USSD:
_gsm911_nc_ss_trans_free(trans);
- conn_usage_token = RAN_CONN_USE_TRANS_NC_SS;
+ usage_token = MSC_A_USE_NC_SS;
+ break;
+ case TRANS_SILENT_CALL:
+ trans_silent_call_free(trans);
+ usage_token = MSC_A_USE_SILENT_CALL;
break;
default:
- conn_usage_token = RAN_CONN_USE_UNTRACKED;
+ usage_token = NULL;
break;
}
if (trans->paging_request) {
- subscr_remove_request(trans->paging_request);
+ paging_request_remove(trans->paging_request);
trans->paging_request = NULL;
}
if (trans->vsub) {
- vlr_subscr_put(trans->vsub, trans_vsub_use(trans->protocol));
+ vlr_subscr_put(trans->vsub, trans_vsub_use(trans->type));
trans->vsub = NULL;
}
- conn = trans->conn;
- trans->conn = NULL;
+ msc_a = trans->msc_a;
+ trans->msc_a = NULL;
+
llist_del(&trans->entry);
talloc_free(trans);
- if (conn)
- ran_conn_put(conn, conn_usage_token);
+ if (msc_a && usage_token)
+ msc_a_put(msc_a, usage_token);
}
/*! allocate an unused transaction ID for the given subscriber
@@ -190,16 +213,17 @@ void trans_free(struct gsm_trans *trans)
* \param[in] protocol Protocol of to be assigned TID
*/
int trans_assign_trans_id(const struct gsm_network *net, const struct vlr_subscr *vsub,
- uint8_t protocol)
+ enum trans_type type)
{
struct gsm_trans *trans;
unsigned int used_tid_bitmask = 0;
int i, j, h;
+ uint8_t proto = trans_type_to_gsm48_proto(type);
/* generate bitmask of already-used TIDs for this (subscr,proto) */
llist_for_each_entry(trans, &net->trans_list, entry) {
if (trans->vsub != vsub ||
- trans->protocol != protocol ||
+ proto != trans_type_to_gsm48_proto(trans->type) ||
trans->transaction_id == TRANS_ID_UNASSIGNED)
continue;
used_tid_bitmask |= (1 << trans->transaction_id);
@@ -222,12 +246,13 @@ int trans_assign_trans_id(const struct gsm_network *net, const struct vlr_subscr
* \param[in] conn Connection to check
* \returns transaction pointer if found, NULL otherwise
*/
-struct gsm_trans *trans_has_conn(const struct ran_conn *conn)
+struct gsm_trans *trans_has_conn(const struct msc_a *msc_a)
{
struct gsm_trans *trans;
+ struct gsm_network *net = msc_a_net(msc_a);
- llist_for_each_entry(trans, &conn->network->trans_list, entry)
- if (trans->conn == conn)
+ llist_for_each_entry(trans, &net->trans_list, entry)
+ if (trans->msc_a == msc_a)
return trans;
return NULL;
@@ -238,19 +263,37 @@ struct gsm_trans *trans_has_conn(const struct ran_conn *conn)
* facilities, which will then send the necessary release indications.
* \param[in] conn Connection that is going to be closed.
*/
-void trans_conn_closed(const struct ran_conn *conn)
+void trans_conn_closed(const struct msc_a *msc_a)
{
- struct gsm_trans *trans;
-
/* As part of the CC REL_IND the remote leg might be released and this
* will trigger another call to trans_free. This is something the llist
* macro can not handle and we need to re-iterate the list every time.
*/
-restart:
- llist_for_each_entry(trans, &conn->network->trans_list, entry) {
- if (trans->conn == conn) {
- trans_free(trans);
- goto restart;
- }
+ struct gsm_trans *trans;
+ while ((trans = trans_has_conn(msc_a)))
+ trans_free(trans);
+}
+
+const struct value_string trans_type_names[] = {
+ { TRANS_CC, "CC" },
+ { TRANS_SMS, "SMS" },
+ { TRANS_USSD, "NCSS" },
+ { TRANS_SILENT_CALL, "silent-call" },
+ {}
+};
+
+uint8_t trans_type_to_gsm48_proto(enum trans_type type)
+{
+ switch (type) {
+ case TRANS_CC:
+ case TRANS_SILENT_CALL:
+ return GSM48_PDISC_CC;
+ case TRANS_SMS:
+ return GSM48_PDISC_SMS;
+ case TRANS_USSD:
+ return GSM48_PDISC_NC_SS;
+ default:
+ return GSM48_PDISC_TEST;
}
+
}