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
diff --git a/include/osmocom/msc/sccp_ran.h b/include/osmocom/msc/sccp_ran.h
new file mode 100644
index 0000000..b7da314
--- /dev/null
+++ b/include/osmocom/msc/sccp_ran.h
@@ -0,0 +1,280 @@
+/* The RAN (Radio Access Network) side of an A- or Iu-connection, which is closely tied to an SCCP connection.
+ * (as opposed to the NAS side.)
+ *
+ * The SCCP connection is located with the MSC-I role, while the MSC-A responsible for subscriber management may be at a
+ * remote MSC behind an E-interface connection. In that case we need to forward the L2 messages over the E-interface and
+ * the BSSAP or RANAP messages get decoded and interpreted at MSC-A.
+ *
+ * The life cycle of a DTAP message from RAN to MSC-A -- starting from the bottom left:
+ *
+ * ------------------>[ 3GPP TS 24.008 ]------------------->|
+ * ^ (Request) (Response) |
+ * | v
+ * msc_a_up_l3() msc_a_tx_dtap_to_i(dtap_msgb)
+ * ^ |
+ * | v
+ * msc_a_nas_decode_cb(struct nas_dec_msg) msc_a_nas_enc(struct nas_enc_msg)
+ * ^ ^ . |
+ * | -Decode NAS- | . NAS v
+ * | | . ran_infra[type]->nas_encode(struct nas_enc_msg)
+ * nas_a_decode_l2() nas_iu_decode_l2() . | |
+ * ^ ^ . v v
+ * | | . nas_a_encode() nas_iu_encode()
+ * ran_infra[type]->nas_dec_l2() | |
+ * ^ | -Encode BSSAP/RANAP- |
+ * | v v
+ * msc_a_nas_dec() msub_tx_an_apdu(from MSC_ROLE_A to MSC_ROLE_I)
+ * ^ |
+ * | MSC-A v
+ * . msc_a FSM . . . . . . . . . . . . . . . . msc_a FSM . . . . . . . . . .
+ * ^ |
+ * | MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST v
+ * | data = an_apdu [possibly
+ * | via GSUP
+ * [possibly from remote MSC-A]
+ * via GSUP |
+ * to remote MSC-A] | MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST
+ * ^ | data = an_apdu
+ * | v
+ * . msc_i FSM . . . . . . . . . . . . . . . . msc_i FSM . . . . . . . . . .
+ * ^ MSC-I |
+ * | MSC_EV_FROM_RAN_UP_L2 V
+ * | data = an_apdu msc_i_down_l2(an_apdu->msg)
+ * | |
+ * ran_peer FSM V
+ * ^ ran_conn_down_l2_co();
+ * | RAN_PEER_EV_MSG_UP_CO |
+ * | data = struct ran_peer_ev_ctx | RAN_PEER_EV_MSG_DOWN_CO
+ * | | data = struct ran_peer_ev_ctx
+ * ran_peer_up_l2() V
+ * (ran_infa->sccp_ran_ops.up_l2) ran_peer FSM
+ * ^ ^ |
+ * | | v
+ * sccp_ran_sap_up() sccp_ran_down_l2_co(conn_id, msg)
+ * ^ ^ | |
+ * | | |SCCP|
+ * |SCCP| v v
+ * | | <------------------------------------------------------
+ * BSC RNC
+ * | |
+ * BTS NodeB
+ * | |
+ * MS UE
+ *
+ * sccp_ran:
+ * - handles receiving of SCCP primitives from the SCCP layer.
+ * - extracts L2 msg
+ * - passes on L2 msg and conn_id by calling sccp_ran_ops.up_l2 == ran_peer_up_l2().
+ *
+ * On Connection-Oriented *Initial* message
+ * ========================================
+ *
+ * ran_peer_up_l2()
+ * - notices an unknown, new osmo_rat_type:conn_id and
+ * - first creates an "empty" msub with new local MSC-I and MSC-A roles;
+ * in this case always a *local* MSC-A (never remote on Initial messages).
+ * - Passes the L2 msgb containing the BSSAP or RANAP as AN-APDU
+ * in MSC_A_EV_FROM_I_COMPLETE_LAYER_3 to the MSC-A role FSM instance.
+ *
+ * MSC-A:
+ * - Receives MSC_A_EV_FROM_I_COMPLETE_LAYER_3 AN-APDU, notices an_proto indicating BSSAP or RANAP.
+ * - Passes L2 message to ran_infra[]->nas_dec_l2(), which decodes the BSSAP or RANAP.
+ * - contained information is passed to msc_a_nas_decode_cb().
+ * - which msc_a starts Complete-L3 and VLR procedures,
+ * - associates msub with a vlr_subscr,
+ * - sends DTAP requests back down by calling msc_a_tx_dtap_to_i() (possibly other more specialized tx functions)
+ * - according to ran_infra[]->nas_encode(), the nas_enc_msg gets encoded as BSSAP or RANAP.
+ * - passes as AN-APDU to MSC-I in MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST signal.
+ *
+ * MSC-I, receiving AN-APDU from local MSC-A:
+ * - feeds L2 msgb to the ran_peer FSM as RAN_PEER_EV_MSG_DOWN_CO, passing the SCCP conn_id.
+ *
+ * sccp_ran_down_l2_co()
+ * - wraps in SCCP prim,
+ * - sends down.
+ *
+ *
+ * On (non-Initial) Connection-Oriented DTAP
+ * =========================================
+ *
+ * ran_peer_up_l2()
+ * - notices an already known conn_id by looking up a matching osmo_rat_type:ran_conn.
+ * - ran_conn already associated with an MSC-I role.
+ * - Now forwards AN-APDU like above, only using MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST.
+ *
+ *
+ * MSC-A and MSC-I roles on separate MSC instances
+ * ===============================================
+ *
+ * After inter-MSC handover, the MSC-I and MSC-A roles can be on separate MSC instances, typically physically distant /
+ * possibly belonging to a different operator. This will never see Complete-L3.
+ * Assuming that both instances are osmo-msc, then:
+ *
+ * At MSC-B:
+ * initially, via GSUP:
+ * - receives Handover Request from remote MSC-A,
+ * - creates msub with local MSC-T role,
+ * - sets up the ran_conn with a new SCCP conn_id, and waits for the MS/UE to show up.
+ * - (fast-forward to successful Handover)
+ * - MSC-T role becomes MSC-I for the remote MSC-A.
+ *
+ * Then for DTAP from the MS:
+ *
+ * sccp_ran:
+ * - receives SCCP,
+ * - extracts L2 and passes on to ran_peer_up_l2().
+ *
+ * ran_peer_up_l2()
+ * - notices an already known conn_id by looking up a matching ran_conn.
+ * - ran_conn already associated with an MSC-I role and an msub.
+ * - forwards AN-APDU in MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST to the MSC-A role.
+ *
+ * At MSC-B, the "MSC-A role" is a *remote* implementation,
+ * meaning there is an msc_a_remote FSM instance in MSC-B's msub:
+ *
+ * MSC-A-Remote:
+ * - msc_a_remote receives MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST,
+ * - wraps AN-APDU in GSUP message,
+ * - sends to remote MSC-A.
+ *
+ * At MSC-A:
+ * Here, msub has a *remote* MSC-I role,
+ * meaning it is an msc_i_remote FSM instance:
+ *
+ * MSC-I-Remote:
+ * - msc_i_remote receives and decodes GSUP message,
+ * - passes AN-APDU to MSC-A FSM instance via MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST.
+ *
+ * MSC-A role:
+ * - Receives MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST, notices an_proto indicating BSSAP or RANAP.
+ * - Passes L2 message to ran_infra[]->nas_dec_l2(), which decodes the BSSAP or RANAP.
+ * - contained information is passed to msc_a_nas_decode_cb().
+ * - sends DTAP requests back down by calling msc_a_tx_dtap_to_i() (possibly other more specialized tx functions)
+ * - according to ran_infra[]->nas_encode(), the nas_enc_msg gets encoded as BSSAP or RANAP.
+ * - passes as AN-APDU to MSC-I in MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST signal.
+ *
+ * MSC-I-Remote:
+ * - msc_i_remote wraps AN-APDU in GSUP message,
+ * - sends to MSC-B
+ *
+ * At MSC-B:
+ * MSC-A-Remote:
+ * - msc_a_remote receives GSUP message,
+ * - passes AN-APDU to msc_i in MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST.
+ *
+ * MSC-I:
+ * - BSSAP or RANAP is indicated both by the AN-APDU an_proto, as well as the ran_conn state for that subscriber.
+ * - feeds L2 msgb to the ran_peer FSM as RAN_PEER_EV_MSG_DOWN_CO, passing the SCCP conn_id.
+ *
+ * sccp_ran_down_l2_co()
+ * - wraps in SCCP prim,
+ * - sends down.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include <osmocom/sigtran/sccp_sap.h>
+
+#include <osmocom/msc/paging.h>
+
+struct msgb;
+struct ran_infra;
+struct sccp_ran_inst;
+
+#define LOG_SCCP_RAN_CO(sri, peer_addr, conn_id, level, fmt, args...) \
+ LOGP((sri) && (sri)->ran? (sri)->ran->log_subsys : DMSC, level, "(%s-%u%s%s) " fmt, \
+ osmo_rat_type_name((sri) && (sri)->ran? (sri)->ran->type : -1), conn_id, \
+ peer_addr ? " from " : "", \
+ peer_addr ? osmo_sccp_inst_addr_name((sri)->sccp, peer_addr) : "", \
+ ## args)
+
+#define LOG_SCCP_RAN_CL_CAT(sri, peer_addr, subsys, level, fmt, args...) \
+ LOGP(subsys, level, "(%s%s%s) " fmt, \
+ osmo_rat_type_name((sri) && (sri)->ran? (sri)->ran->type : -1), \
+ peer_addr ? " from " : "", \
+ peer_addr ? osmo_sccp_inst_addr_name((sri)->sccp, peer_addr) : "", \
+ ## args)
+
+#define LOG_SCCP_RAN_CL(sri, peer_addr, level, fmt, args...) \
+ LOG_SCCP_RAN_CL_CAT(sri, peer_addr, (sri) && (sri)->ran? (sri)->ran->log_subsys : DMSC, level, fmt, ##args)
+
+#define LOG_SCCP_RAN_CAT(sri, subsys, level, fmt, args...) \
+ LOG_SCCP_RAN_CL_CAT(sri, NULL, subsys, level, fmt, ##args)
+
+#define LOG_SCCP_RAN(sri, level, fmt, args...) \
+ LOG_SCCP_RAN_CL(sri, NULL, level, fmt, ##args)
+
+extern struct osmo_tdef g_sccp_tdefs[];
+
+enum reset_msg_type {
+ SCCP_RAN_MSG_NON_RESET = 0,
+ SCCP_RAN_MSG_RESET,
+ SCCP_RAN_MSG_RESET_ACK,
+};
+
+struct sccp_ran_ops {
+ /* Implemented to receive L2 messages (e.g. BSSAP or RANAP passed to ran_peer).
+ * - ConnectionLess messages: co = false, calling_addr != NULL, conn_id == 0;
+ * - ConnectionOriented Initial messages: co = true, calling_addr != NULL;
+ * - ConnectionOriented non-Initial messages: co = true, calling_addr == NULL;
+ */
+ int (* up_l2 )(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *calling_addr, bool co, uint32_t conn_id,
+ struct msgb *l2);
+
+ /* Implemented to finally remove a connection state. Last event in a connection-oriented exchange. If the
+ * N-DISCONNECT contained l2 data, it was dispatched via up_l2() before this is called. */
+ void (* disconnect )(struct sccp_ran_inst *sri, uint32_t conn_id);
+
+ /* Return whether the given l2_cl message is a RESET, RESET ACKNOWLEDGE, or RESET-unrelated message.
+ * This callback is stored in struct sccp_ran_inst to provide RESET handling to the caller (ran_peer),
+ * it is not used in sccp_ran.c. */
+ enum reset_msg_type (* is_reset_msg )(const struct sccp_ran_inst *sri, const struct msgb *l2_cl);
+
+ /* Return a RESET or RESET ACK message for this RAN type.
+ * This callback is stored in struct sccp_ran_inst to provide RESET handling to the caller (ran_peer),
+ * it is not used in sccp_ran.c. */
+ struct msgb* (* make_reset_msg )(const struct sccp_ran_inst *sri, enum reset_msg_type);
+
+ /* Return a PAGING message towards the given Cell Identifier, to page for the given TMSI or IMSI.
+ * Page for TMSI if TMSI != GSM_RESERVED_TMSI, otherwise page for IMSI. */
+ struct msgb* (* 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 a human printable name for the msgb */
+ const char* (* msg_name )(const struct sccp_ran_inst *sri, const struct msgb *l2);
+};
+
+struct sccp_ran_inst {
+ struct ran_infra *ran;
+
+ struct osmo_sccp_instance *sccp;
+ struct osmo_sccp_user *scu;
+ struct osmo_sccp_addr local_sccp_addr;
+
+ struct llist_head ran_peers;
+ struct llist_head ran_conns;
+
+ void *user_data;
+
+ /* Compatibility with legacy osmo-hnbgw that was unable to properly handle RESET messages. Set to 'false' to
+ * require proper RESET procedures, set to 'true' to implicitly put a ran_peer in RAN_PEER_ST_READY upon the
+ * first CO message. Default is false = be strict. */
+ bool ignore_missing_reset;
+};
+
+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);
+
+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);
+int sccp_ran_down_l2_co(struct sccp_ran_inst *sri, uint32_t conn_id, struct msgb *l2);
+int sccp_ran_down_l2_cl(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *called_addr, struct msgb *l2);
+
+int sccp_ran_disconnect(struct sccp_ran_inst *ran, uint32_t conn_id, uint32_t cause);