aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2018-12-07 14:47:34 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2019-05-06 23:45:28 +0200
commit62cd38da6928199ec87cf098c169268681e0e2d7 (patch)
tree31effc8d495f68964d1151f17a83f949d48cc238
parent5b1e0309b513ebe05a4b5b6dfa0c8ad620d34a99 (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
-rw-r--r--configure.ac1
-rw-r--r--doc/sequence_charts/Makefile.am20
-rw-r--r--doc/sequence_charts/inter_bsc_ho.msc36
-rw-r--r--doc/sequence_charts/inter_msc_ho.msc82
-rw-r--r--doc/sequence_charts/mncc_fsm.msc84
-rw-r--r--include/osmocom/msc/Makefile.am32
-rw-r--r--include/osmocom/msc/a_iface.h83
-rw-r--r--include/osmocom/msc/a_iface_bssap.h41
-rw-r--r--include/osmocom/msc/a_reset.h31
-rw-r--r--include/osmocom/msc/call_leg.h81
-rw-r--r--include/osmocom/msc/cell_id_list.h43
-rw-r--r--include/osmocom/msc/e_link.h36
-rw-r--r--include/osmocom/msc/gsm_04_08.h37
-rw-r--r--include/osmocom/msc/gsm_04_11.h5
-rw-r--r--include/osmocom/msc/gsm_04_11_gsup.h7
-rw-r--r--include/osmocom/msc/gsm_04_14.h14
-rw-r--r--include/osmocom/msc/gsm_04_80.h15
-rw-r--r--include/osmocom/msc/gsm_09_11.h10
-rw-r--r--include/osmocom/msc/gsm_data.h41
-rw-r--r--include/osmocom/msc/gsm_data_shared.h6
-rw-r--r--include/osmocom/msc/gsm_subscriber.h36
-rw-r--r--include/osmocom/msc/gsup_client_mux.h34
-rw-r--r--include/osmocom/msc/iu_dummy.h50
-rw-r--r--include/osmocom/msc/iucs.h14
-rw-r--r--include/osmocom/msc/iucs_ranap.h7
-rw-r--r--include/osmocom/msc/mncc.h14
-rw-r--r--include/osmocom/msc/mncc_call.h140
-rw-r--r--include/osmocom/msc/msc_a.h215
-rw-r--r--include/osmocom/msc/msc_a_remote.h17
-rw-r--r--include/osmocom/msc/msc_common.h39
-rw-r--r--include/osmocom/msc/msc_ho.h104
-rw-r--r--include/osmocom/msc/msc_i.h46
-rw-r--r--include/osmocom/msc/msc_i_remote.h14
-rw-r--r--include/osmocom/msc/msc_ifaces.h39
-rw-r--r--include/osmocom/msc/msc_mgcp.h65
-rw-r--r--include/osmocom/msc/msc_roles.h387
-rw-r--r--include/osmocom/msc/msc_t.h60
-rw-r--r--include/osmocom/msc/msc_t_remote.h14
-rw-r--r--include/osmocom/msc/msub.h79
-rw-r--r--include/osmocom/msc/neighbor_ident.h68
-rw-r--r--include/osmocom/msc/paging.h46
-rw-r--r--include/osmocom/msc/ran_conn.h243
-rw-r--r--include/osmocom/msc/ran_infra.h31
-rw-r--r--include/osmocom/msc/ran_msg.h281
-rw-r--r--include/osmocom/msc/ran_msg_a.h45
-rw-r--r--include/osmocom/msc/ran_msg_iu.h35
-rw-r--r--include/osmocom/msc/ran_peer.h106
-rw-r--r--include/osmocom/msc/rtp_stream.h64
-rw-r--r--include/osmocom/msc/sccp_ran.h280
-rw-r--r--include/osmocom/msc/sgs_iface.h9
-rw-r--r--include/osmocom/msc/signal.h20
-rw-r--r--include/osmocom/msc/silent_call.h13
-rw-r--r--include/osmocom/msc/sms_queue.h1
-rw-r--r--include/osmocom/msc/transaction.h55
-rw-r--r--include/osmocom/msc/vlr.h42
-rw-r--r--include/osmocom/msc/vlr_sgs.h2
-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.c356
-rw-r--r--src/libmsc/cell_id_list.c76
-rw-r--r--src/libmsc/e_link.c380
-rw-r--r--src/libmsc/gsm_04_08.c1353
-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.c220
-rw-r--r--src/libmsc/msub.c587
-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.c124
-rw-r--r--src/libvlr/vlr.c204
-rw-r--r--src/libvlr/vlr_access_req_fsm.c34
-rw-r--r--src/libvlr/vlr_lu_fsm.c28
-rw-r--r--src/libvlr/vlr_sgs.c10
-rw-r--r--src/libvlr/vlr_sgs_fsm.c6
-rw-r--r--src/osmo-msc/Makefile.am1
-rw-r--r--src/osmo-msc/msc_main.c156
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/msc_vlr/Makefile.am16
-rw-r--r--tests/msc_vlr/msc_vlr_test_authen_reuse.c40
-rw-r--r--tests/msc_vlr/msc_vlr_test_authen_reuse.err4351
-rw-r--r--tests/msc_vlr/msc_vlr_test_call.c60
-rw-r--r--tests/msc_vlr/msc_vlr_test_call.err2918
-rw-r--r--tests/msc_vlr/msc_vlr_test_gsm_authen.c203
-rw-r--r--tests/msc_vlr/msc_vlr_test_gsm_authen.err4566
-rw-r--r--tests/msc_vlr/msc_vlr_test_gsm_ciph.c249
-rw-r--r--tests/msc_vlr/msc_vlr_test_gsm_ciph.err5674
-rw-r--r--tests/msc_vlr/msc_vlr_test_hlr_reject.c104
-rw-r--r--tests/msc_vlr/msc_vlr_test_hlr_reject.err2023
-rw-r--r--tests/msc_vlr/msc_vlr_test_hlr_timeout.c12
-rw-r--r--tests/msc_vlr/msc_vlr_test_hlr_timeout.err318
-rw-r--r--tests/msc_vlr/msc_vlr_test_ms_timeout.c53
-rw-r--r--tests/msc_vlr/msc_vlr_test_ms_timeout.err1065
-rw-r--r--tests/msc_vlr/msc_vlr_test_no_authen.c173
-rw-r--r--tests/msc_vlr/msc_vlr_test_no_authen.err4147
-rw-r--r--tests/msc_vlr/msc_vlr_test_reject_concurrency.c61
-rw-r--r--tests/msc_vlr/msc_vlr_test_reject_concurrency.err3170
-rw-r--r--tests/msc_vlr/msc_vlr_test_rest.c42
-rw-r--r--tests/msc_vlr/msc_vlr_test_rest.err888
-rw-r--r--tests/msc_vlr/msc_vlr_test_ss.c30
-rw-r--r--tests/msc_vlr/msc_vlr_test_ss.err731
-rw-r--r--tests/msc_vlr/msc_vlr_test_umts_authen.c78
-rw-r--r--tests/msc_vlr/msc_vlr_test_umts_authen.err3304
-rw-r--r--tests/msc_vlr/msc_vlr_tests.c771
-rw-r--r--tests/msc_vlr/msc_vlr_tests.h47
-rw-r--r--tests/sms_queue/Makefile.am1
-rw-r--r--tests/sms_queue/sms_queue_test.c1
-rw-r--r--tests/test_neighbor_ident.vty237
-rw-r--r--tests/test_nodes.vty4
151 files changed, 35637 insertions, 22975 deletions
diff --git a/configure.ac b/configure.ac
index 8ab60213e..ae6dd6ac2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -256,6 +256,7 @@ AC_OUTPUT(
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile
+ doc/sequence_charts/Makefile
contrib/Makefile
contrib/systemd/Makefile
Makefile)
diff --git a/doc/sequence_charts/Makefile.am b/doc/sequence_charts/Makefile.am
new file mode 100644
index 000000000..75e250a5f
--- /dev/null
+++ b/doc/sequence_charts/Makefile.am
@@ -0,0 +1,20 @@
+all: msc dot
+
+msc: \
+ $(builddir)/mncc_fsm.png \
+ $(builddir)/inter_bsc_ho.png \
+ $(builddir)/inter_msc_ho.png \
+ $(NULL)
+
+dot: \
+ $(NULL)
+
+$(builddir)/%.png: $(srcdir)/%.msc
+ mscgen -T png -o $@ $<
+
+$(builddir)/%.png: $(srcdir)/%.dot
+ dot -Tpng $< > $@
+
+.PHONY: poll
+poll:
+ while true; do $(MAKE) msc dot; sleep 1; done
diff --git a/doc/sequence_charts/inter_bsc_ho.msc b/doc/sequence_charts/inter_bsc_ho.msc
new file mode 100644
index 000000000..2bc1276ef
--- /dev/null
+++ b/doc/sequence_charts/inter_bsc_ho.msc
@@ -0,0 +1,36 @@
+msc {
+ hscale=2;
+ bsca [label="BSC-A"], i[label="MSC-I"], a[label="MSC-A"], t[label="MSC-T"], bscb[label="BSC-B"];
+
+ i note t [label="'MSC-A,I,T' are explained in 3GPP TS 49.008 '4.3 Roles of MSC-A, MSC-I and MSC-T'"];
+ i note i [label="I = Internal; the MSC that does BSSMAP to the BSC (may change to a remote MSC-B after HO)"];
+ a note a [label="A = first; the MSC that has MM control BSSMAP to the BSC (never changes)"];
+ t note t [label="T = transitory; the MSC that a handover is going towards (may be MSC-A for inter-BSC HO, may be a remote MSC-B)"];
+
+ bsca => i [label="BSSMAP Handover Required"];
+ i -> a [label="BSSMAP Handover Required"];
+
+ a -> t [label="MAP Prepare Handover"];
+ t => bscb [label="BSSMAP Handover Request"];
+ t <= bscb [label="BSSMAP Handover Request ACK"];
+ a <- t [label="MAP Prepare Handover Response"];
+ i <- a [label="MAP Prepare Handover Response"];
+ bsca <= i [label="BSSMAP Handover Command"];
+
+ --- [label="MS sends RACH to new cell"];
+
+ t <= bscb [label="BSSMAP Handover Detected"];
+ a <- t [label="MAP Access Signaling Request"];
+
+ t <= bscb [label="BSSMAP Handover Complete"];
+ a <- t [label="MAP Send End Signal"];
+
+
+ a abox a [label="MSC-A accepts the new BSC"];
+ i note t [label="previous MSC-I gets dropped, MSC-T becomes the new MSC-I"];
+ i abox i [label="discard"];
+ t abox t [label="MSC-I"];
+
+ bsca <= i [label="BSSMAP Clear Command"];
+
+}
diff --git a/doc/sequence_charts/inter_msc_ho.msc b/doc/sequence_charts/inter_msc_ho.msc
new file mode 100644
index 000000000..f7572d1b5
--- /dev/null
+++ b/doc/sequence_charts/inter_msc_ho.msc
@@ -0,0 +1,82 @@
+msc {
+ hscale=2;
+ bsca [label="BSC-A"], ai[label="MSC-I (at MSC-A)"], a[label="MSC-A"], bt[label="MSC-T (at MSC-B)"], bi[label="MSC-I (at MSC-B)"], bscb[label="BSC-B"],
+ ct[label="MSC-T (at MSC-C)"], bscc[label="BSC-C"];
+
+ ai note bt [label="'MSC-A,I,T' are explained in 3GPP TS 49.008 '4.3 Roles of MSC-A, MSC-I and MSC-T'"];
+ ai note ai [label="I = Internal; the MSC that does BSSMAP to the BSC (may change to a remote MSC-B after HO)"];
+ a note a [label="A = first; the MSC that has MM control (never changes)"];
+ bt note bi [label="B = second; the MSC that acts as BSS relay for MSC-A"];
+ bt note bt [label="T = transitory; the MSC that a handover is going towards (may be MSC-A for inter-BSC HO, may be a remote MSC-B)"];
+
+ bsca => ai [label="BSSMAP Handover Required"];
+ ai -> a [label="BSSMAP Handover Required"];
+
+ a rbox a [label="MSC-A routes all MSC-T messages to remote MSC-B."];
+
+ a => bt [label="MAP Prepare Handover"];
+ a note bt [label="in Osmocom, MAP messages are actually sent as GSUP, to the HLR"];
+
+ bt rbox bt [label="MSC-B routes all MSC-A type messages back to MSC-A."];
+ bt rbox bt [label="MSC-B generates a Handover MSISDN"];
+
+ bt => bscb [label="BSSMAP Handover Request"];
+ bt <= bscb [label="BSSMAP Handover Request ACK"];
+ a <= bt [label="MAP Prepare Handover Response"];
+
+ a => bt [label="establish SIP call to Handover MSISDN via external MNCC"];
+
+ ai <- a [label="BSSMAP Handover Command"];
+ bsca <= ai [label="BSSMAP Handover Command"];
+
+ --- [label="MS sends RACH to new cell"];
+
+ bt <= bscb [label="BSSMAP Handover Detected"];
+ a <= bt [label="MAP Access Signaling Request"];
+
+ bt <= bscb [label="BSSMAP Handover Complete"];
+ a <= bt [label="MAP Send End Signal"];
+ bt rbox bt [label="MSC-B drops the generated Handover MSISDN"];
+
+ a abox a [label="MSC-A accepts the new MSC-B and BSC"];
+ ai note bt [label="previous MSC-I (A) gets dropped, MSC-T (B) becomes the new MSC-I"];
+ ai rbox a [label="MSC-A performs all MSC-I tasks via MAP at remote MSC-B's MSC-I."];
+ bt abox bi [label="MSC-I"];
+ ai abox ai [label="discard"];
+ bsca <= ai [label="BSSMAP Clear Command"];
+
+ ...;
+ ...;
+
+ --- [label="Another inter-MSC handover"];
+ a note bi [label="MSC-A remains in charge"];
+
+ bscb => bi [label="BSSMAP Handover Required"];
+ bi => a [label="BSSMAP Handover Required"];
+
+ a rbox a [label="MSC-A routes all MSC-T messages to remote MSC-C."];
+
+ a => ct [label="MAP Prepare Handover"];
+ ct rbox ct [label="MSC-C routes all MSC-A type messages back to MSC-A."];
+ ct => bscc [label="BSSMAP Handover Request"];
+ ct <= bscc [label="BSSMAP Handover Request ACK"];
+ a <= ct [label="MAP Prepare Handover Response"];
+ a => bi [label="MAP Prepare Handover Response"];
+ bscb <= bi [label="BSSMAP Handover Command"];
+
+ --- [label="MS sends RACH to new cell"];
+
+ ct <= bscc [label="BSSMAP Handover Detected"];
+ a <= ct [label="MAP Access Signaling Request"];
+
+ ct <= bscc [label="BSSMAP Handover Complete"];
+ a <= ct [label="MAP Send End Signal"];
+
+ a abox a [label="MSC-A accepts the new MSC-C and BSC"];
+ bi note ct [label="previous MSC-I (B) gets dropped, MSC-T (B) becomes the new MSC-I"];
+ ai rbox a [label="MSC-A performs all MSC-I tasks via MAP at remote MSC-C's MSC-I."];
+ ct abox ct [label="MSC-I"];
+ bi abox bi [label="discard"];
+ bscb <= bi [label="BSSMAP Clear Command"];
+
+}
diff --git a/doc/sequence_charts/mncc_fsm.msc b/doc/sequence_charts/mncc_fsm.msc
new file mode 100644
index 000000000..ae5e0a211
--- /dev/null
+++ b/doc/sequence_charts/mncc_fsm.msc
@@ -0,0 +1,84 @@
+msc {
+ hscale=2;
+ msc1[label="osmo-msc"], mncc1[label="MNCC FSM"], pbx[label="MNCC server (osmo-sip-connector)"], mncc2[label="MNCC FSM"], msc2[label="osmo-msc"];
+
+ mncc1 note mncc1 [label="The typical progression of an outgoing call, i.e. a call initiated by osmo-msc, as
+ implemented in mncc_fsm.h, mncc_fsm.c"];
+ mncc2 note mncc2 [label="The typical progression of an incoming call, i.e. a call initiated by the PBX, as
+ implemented in mncc_fsm.h, mncc_fsm.c"];
+
+ mncc1 abox mncc1 [label="MNCC_ST_NOT_STARTED"];
+ msc1 rbox msc1 [label="mncc_outgoing_start()"];
+ msc1 -> mncc1 [label="MNCC_EV_OUTGOING_START"];
+
+ mncc1 abox mncc1 [label="MNCC_ST_OUTGOING_WAIT_PROCEEDING"];
+ mncc1 => pbx [label="MNCC_SETUP_IND
+ \n callref, IMSI, called and calling number"];
+ mncc1 <= pbx [label="MNCC_RTP_CREATE
+ \n callref"];
+ mncc1 rbox mncc1 [label="mncc_rx_rtp_create()"];
+ mncc1 => pbx [label="MNCC_RTP_CREATE
+ \n callref, RTP IP address and port"];
+ mncc1 <= pbx [label="MNCC_CALL_PROC_REQ
+ \n callref, RTP IP address and port"];
+ mncc1 abox mncc1 [label="MNCC_ST_OUTGOING_WAIT_COMPLETE"];
+
+ msc2 <= pbx [label="MNCC_SETUP_REQ
+ \n callref, called and calling number"];
+ mncc2 abox mncc2 [label="MNCC_ST_NOT_STARTED"];
+ msc2 rbox msc2 [label="mncc_incoming_start()"];
+ msc2 -> mncc2 [label="MNCC_EV_INCOMING_START"];
+ mncc2 abox mncc2 [label="MNCC_ST_INCOMING_WAIT_COMPLETE"];
+ mncc2 => pbx [label="MNCC_CALL_CONF_IND
+ \n callref, bearer capabilities, cccap and IMSI"];
+ mncc2 <= pbx [label="MNCC_RTP_CREATE
+ \n callref"];
+ mncc2 rbox mncc2 [label="mncc_rx_rtp_create()"];
+ mncc2 => pbx [label="MNCC_RTP_CREATE
+ \n callref, RTP IP address and port"];
+ mncc2 => pbx [label="MNCC_ALERT_IND
+ \n callref"];
+
+ mncc1 <= pbx [label="MNCC_ALERT_REQ
+ \n callref and progress"];
+
+ mncc2 => pbx [label="MNCC_SETUP_CNF
+ \n callref, imsi and connected number"];
+ mncc2 <= pbx [label="MNCC_RTP_CONNECT
+ \n callref, RTP IP and port"];
+ mncc2 rbox mncc2 [label="mncc_rx_rtp_connect()"];
+ mncc2 <= pbx [label="MNCC_SETUP_COMPL_REQ
+ \n callref"];
+ mncc2 abox mncc2 [label="MNCC_ST_TALKING"];
+
+ mncc1 <= pbx [label="MNCC_RTP_CONNECT
+ \n callref, RTP IP and port"];
+ mncc1 rbox mncc1 [label="mncc_rx_rtp_connect()"];
+ msc1 <- mncc1 [label="rtp_stream_set_remote_addr()"];
+ mncc1 <= pbx [label="MNCC_SETUP_RSP
+ \n callref"];
+ mncc1 => pbx [label="MNCC_SETUP_COMPL_IND
+ \n callref"];
+ mncc1 abox mncc1 [label="MNCC_ST_TALKING"];
+
+ ...;
+ ... [label="Call goes on for a while..."];
+ ...;
+
+ mncc1 rbox mncc1 [label="mncc_release()"];
+ mncc1 => pbx [label="MNCC_DISC_IND
+ \n callref and cause"];
+ mncc1 abox mncc1 [label="MNCC_ST_WAIT_RELEASE_ACK"];
+ mncc1 <= pbx [label="MNCC_REL_REQ
+ \n callref and cause"];
+
+ mncc2 <= pbx [label="MNCC_DISC_REQ
+ \n callref and cause"];
+ mncc2 => pbx [label="MNCC_REL_IND
+ \n callref and cause"];
+ mncc2 abox mncc2 [label="terminated"];
+
+ mncc1 => pbx [label="MNCC_REL_CNF
+ \n callref"];
+ mncc1 abox mncc1 [label="terminated"];
+}
diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am
index 408d710e3..729dae444 100644
--- a/include/osmocom/msc/Makefile.am
+++ b/include/osmocom/msc/Makefile.am
@@ -1,28 +1,42 @@
noinst_HEADERS = \
- a_iface.h \
- a_iface_bssap.h \
+ call_leg.h \
+ cell_id_list.h \
db.h \
debug.h \
+ e_link.h \
gsm_04_08.h \
- gsm_04_11.h \
gsm_04_11_gsup.h \
+ gsm_04_11.h \
gsm_04_14.h \
gsm_04_80.h \
gsm_09_11.h \
gsm_data.h \
gsm_data_shared.h \
gsm_subscriber.h \
- iucs.h \
- iucs_ranap.h \
- iu_dummy.h \
mncc.h \
mncc_int.h \
+ mncc_call.h \
+ msc_a.h \
+ msc_a_remote.h \
msc_common.h \
- msc_ifaces.h \
- msc_mgcp.h \
- a_reset.h \
+ msc_ho.h \
+ msc_i.h \
+ msc_i_remote.h \
+ msc_roles.h \
+ msc_t.h \
+ msc_t_remote.h \
+ msub.h \
+ neighbor_ident.h \
+ paging.h \
ran_conn.h \
+ ran_infra.h \
+ ran_msg.h \
+ ran_msg_a.h \
+ ran_msg_iu.h \
+ ran_peer.h \
rrlp.h \
+ rtp_stream.h \
+ sccp_ran.h \
sgs_iface.h \
sgs_server.h \
sgs_vty.h \
diff --git a/include/osmocom/msc/a_iface.h b/include/osmocom/msc/a_iface.h
deleted file mode 100644
index d8a8aab38..000000000
--- a/include/osmocom/msc/a_iface.h
+++ /dev/null
@@ -1,83 +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/>.
- *
- */
-
-#pragma once
-
-#include <osmocom/msc/a_reset.h>
-#include <osmocom/msc/transaction.h>
-#include <osmocom/msc/vlr.h>
-#include <osmocom/gsm/protocol/gsm_08_08.h>
-
-/* A struct to keep a context information about the BSCs we are associated with */
-struct bsc_context {
- struct llist_head list;
-
- /* Holds a copy of the sccp address of the BSC,
- * this address will become known as soon as
- * a remote BSC tries to make a connection or
- * sends a RESET request via UNIDATA */
- struct osmo_sccp_addr bsc_addr;
-
- /* Holds a copy of the our local MSC address,
- * this will be the sccp-address that is associated
- * with the A interface */
- struct osmo_sccp_addr msc_addr;
-
- /* A pointer to the reset handler FSM, the
- * state machine is allocated when the BSC
- * is registerd. */
- struct osmo_fsm_inst *reset_fsm;
-
- /* A pointer to the sccp_user that is associated
- * with the A interface. We need this information
- * to send the resets and to send paging requests */
- struct osmo_sccp_user *sccp_user;
-};
-
-/* Initalize A interface connection between to MSC and BSC */
-int a_init(struct osmo_sccp_instance *sccp, struct gsm_network *network);
-
-/* Send DTAP message via A-interface, take ownership of msg */
-int a_iface_tx_dtap(struct msgb *msg);
-
-/* 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);
-
-/* Page a subscriber via A-interface */
-int a_iface_tx_paging(const char *imsi, uint32_t tmsi, uint16_t lac);
-
-/* Send assignment request via A-interface */
-int a_iface_tx_assignment(const struct gsm_trans *trans);
-
-/* Send clear command via A-interface */
-int a_iface_tx_clear_cmd(const struct ran_conn *conn);
-
-int a_iface_tx_classmark_request(const struct ran_conn *conn);
-
-/* Clear all RAN connections on a specified BSC
- * (Helper function for a_iface_bssap.c) */
-void a_clear_all(struct osmo_sccp_user *scu, const struct osmo_sccp_addr *bsc_addr);
-
-void a_start_reset(struct bsc_context *bsc_ctx, bool already_connected);
-
-/* Delete info of a closed connection from the active connection list
- * (Helper function for a_iface_bssap.c) */
-void a_delete_bsc_con(uint32_t conn_id);
diff --git a/include/osmocom/msc/a_iface_bssap.h b/include/osmocom/msc/a_iface_bssap.h
deleted file mode 100644
index d4b67e3ec..000000000
--- a/include/osmocom/msc/a_iface_bssap.h
+++ /dev/null
@@ -1,41 +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/>.
- *
- */
-
-#pragma once
-
-#include <osmocom/msc/a_iface.h>
-
-/* Note: The structs and functions presented in this header file are intended
- * to be used only by a_iface.c. */
-
-/* A structure to hold tha most basic information about a sigtran connection
- * we use this struct internally here to pass connection data around */
-struct a_conn_info {
- struct bsc_context *bsc;
- uint32_t conn_id;
- struct gsm_network *network;
-};
-
-/* 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);
-
-/* Receive incoming connection oriented data messages via sccp */
-int a_sccp_rx_dt(struct osmo_sccp_user *scu, const struct a_conn_info *a_conn_info, struct msgb *msg);
-
diff --git a/include/osmocom/msc/a_reset.h b/include/osmocom/msc/a_reset.h
deleted file mode 100644
index 8eb3bbfda..000000000
--- a/include/osmocom/msc/a_reset.h
+++ /dev/null
@@ -1,31 +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/>.
- *
- */
-
-#pragma once
-
-/* 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);
-
-/* Confirm that we sucessfully received a reset acknowlege message */
-void a_reset_ack_confirm(struct osmo_fsm_inst *reset_fsm);
-
-/* Check if we have a connection to a specified msc */
-bool a_reset_conn_ready(struct osmo_fsm_inst *reset_fsm);
diff --git a/include/osmocom/msc/call_leg.h b/include/osmocom/msc/call_leg.h
new file mode 100644
index 000000000..b8126e82d
--- /dev/null
+++ b/include/osmocom/msc/call_leg.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/tdef.h>
+
+struct osmo_fsm_inst;
+struct osmo_sockaddr_str;
+struct osmo_mgcpc_ep;
+struct gsm_network;
+struct gsm_trans;
+struct rtp_stream;
+enum rtp_direction;
+
+extern struct osmo_tdef g_mgw_tdefs[];
+
+/* All sides of an MGW endpoint, connecting remote RTP peers via the MGW.
+ *
+ * BSC MGW PBX
+ * CI CI
+ * [MGW-endpoint]
+ * [--rtp_stream--] [--rtp_stream--]
+ * [----------------call_leg----------------]
+ *
+ */
+struct call_leg {
+ struct osmo_fsm_inst *fi;
+
+ struct osmo_mgcpc_ep *mgw_endpoint;
+
+ /* Array indexed by enum rtp_direction. */
+ struct rtp_stream *rtp[2];
+ /* Array indexed by enum rtp_direction. */
+ enum mgcp_connection_mode crcx_conn_mode[2];
+
+ uint32_t parent_event_rtp_addr_available;
+ uint32_t parent_event_rtp_complete;
+ uint32_t parent_event_rtp_released;
+
+ /* For internal MNCC, if RTP addresses for endpoints become assigned by the MGW, implicitly notify the other
+ * call leg's RTP_TO_CN side rtp_stream with rtp_stream_remote_addr_available(). */
+ struct call_leg *local_bridge;
+
+ /* Prevent events from deallocating for certain release code paths, to prevent use-after-free problems. */
+ bool deallocating;
+};
+
+enum call_leg_event {
+ CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE,
+ CALL_LEG_EV_RTP_STREAM_ESTABLISHED,
+ CALL_LEG_EV_RTP_STREAM_GONE,
+ CALL_LEG_EV_MGW_ENDPOINT_GONE,
+};
+
+void call_leg_init(struct gsm_network *net);
+
+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);
+
+void call_leg_reparent(struct call_leg *cl,
+ 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);
+
+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);
+
+int call_leg_ensure_rtp_alloc(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id,
+ struct gsm_trans *for_trans);
+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_port_if_known);
+struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direction dir);
+
+void call_leg_rtp_stream_gone(struct call_leg *cl, struct rtp_stream *rtps);
+void call_leg_release(struct call_leg *cl);
diff --git a/include/osmocom/msc/cell_id_list.h b/include/osmocom/msc/cell_id_list.h
new file mode 100644
index 000000000..83d05f5da
--- /dev/null
+++ b/include/osmocom/msc/cell_id_list.h
@@ -0,0 +1,43 @@
+/* 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.
+ */
+
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+
+struct cell_id_list_entry {
+ struct llist_head entry;
+ struct gsm0808_cell_id cell_id;
+};
+
+int cell_id_list_add_cell(void *talloc_ctx, struct llist_head *list, const struct gsm0808_cell_id *cid);
+int cell_id_list_add_list(void *talloc_ctx, struct llist_head *list, const struct gsm0808_cell_id_list2 *cil);
+
+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);
+
+void cell_id_list_del_entry(struct cell_id_list_entry *e);
diff --git a/include/osmocom/msc/e_link.h b/include/osmocom/msc/e_link.h
new file mode 100644
index 000000000..88e41a7aa
--- /dev/null
+++ b/include/osmocom/msc/e_link.h
@@ -0,0 +1,36 @@
+/* E-interface messaging over a GSUP connection */
+#pragma once
+
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/msc/msc_roles.h>
+
+struct osmo_fsm_inst;
+struct gsm_network;
+struct vlr_instance;
+
+/* E-interface: connection to a remote MSC via GSUP */
+struct e_link {
+ struct osmo_fsm_inst *msc_role;
+ struct gsup_client_mux *gcm;
+ uint8_t *remote_name;
+ size_t remote_name_len;
+};
+
+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);
+void e_link_assign(struct e_link *e, struct osmo_fsm_inst *msc_role);
+void e_link_free(struct e_link *e);
+
+int e_prep_gsup_msg(struct e_link *e, enum msc_role from_role, struct osmo_gsup_message *gsup_msg);
+int e_tx(struct e_link *e, const struct osmo_gsup_message *gsup_msg);
+
+const char *e_link_name(struct e_link *e);
+
+void msc_a_i_t_gsup_init(struct gsm_network *net);
+
+enum osmo_gsup_entity msc_role_to_gsup_entity(enum msc_role role);
+enum msc_role gsup_entity_to_msc_role(enum osmo_gsup_entity entity);
+int gsup_msg_assign_an_apdu(struct osmo_gsup_message *gsup_msg, struct an_apdu *an_apdu);
+
+struct msgb *gsup_msg_to_msgb(const struct osmo_gsup_message *gsup_msg);
+void gsup_msg_to_an_apdu(struct an_apdu *an_apdu, const struct osmo_gsup_message *gsup_msg);
diff --git a/include/osmocom/msc/gsm_04_08.h b/include/osmocom/msc/gsm_04_08.h
index 2d4a0cd77..47747cbcf 100644
--- a/include/osmocom/msc/gsm_04_08.h
+++ b/include/osmocom/msc/gsm_04_08.h
@@ -4,6 +4,7 @@
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/msc/transaction.h>
struct msgb;
struct gsm_bts;
@@ -12,6 +13,7 @@ struct gsm_trans;
struct ran_conn;
struct amr_multirate_conf;
struct amr_mode;
+struct msc_a;
#define GSM48_ALLOC_SIZE 2048
#define GSM48_ALLOC_HEADROOM 256
@@ -22,33 +24,26 @@ static inline struct msgb *gsm48_msgb_alloc_name(const char *name)
name);
}
-void cm_service_request_concludes(struct ran_conn *conn,
- struct msgb *msg);
+void cm_service_request_concludes(struct msc_a *msc_a, struct msgb *msg, enum osmo_cm_service_type type);
/* config options controlling the behaviour of the lower leves */
-void gsm0408_clear_all_trans(struct gsm_network *net, int protocol);
-int gsm0408_dispatch(struct ran_conn *conn, struct msgb *msg);
+void gsm0408_clear_all_trans(struct gsm_network *net, enum trans_type type);
int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id);
/* don't use "enum gsm_chreq_reason_t" to avoid circular dependency */
void gsm_net_update_ctype(struct gsm_network *net);
-int gsm48_tx_simple(struct ran_conn *conn,
- uint8_t pdisc, uint8_t msg_type);
-int gsm48_tx_mm_info(struct ran_conn *conn);
-int gsm48_tx_mm_auth_req(struct ran_conn *conn, uint8_t *rand,
- uint8_t *autn, int key_seq);
-int gsm48_tx_mm_auth_rej(struct ran_conn *conn);
-int gsm48_tx_mm_serv_ack(struct ran_conn *conn);
-int gsm48_tx_mm_serv_rej(struct ran_conn *conn,
- enum gsm48_reject_value value);
+int gsm48_tx_simple(struct msc_a *msc_a, uint8_t pdisc, uint8_t msg_type);
+int gsm48_tx_mm_info(struct msc_a *msc_a);
+int gsm48_tx_mm_auth_req(struct msc_a *msc_a, uint8_t *rand, uint8_t *autn, int key_seq);
+int gsm48_tx_mm_auth_rej(struct msc_a *msc_a);
+int gsm48_tx_mm_serv_ack(struct msc_a *msc_a);
+int gsm48_tx_mm_serv_rej(struct msc_a *msc_a, enum gsm48_reject_value value);
int gsm48_send_rr_release(struct gsm_lchan *lchan);
int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv);
-int gsm48_send_rr_app_info(struct ran_conn *conn, uint8_t apdu_id,
- uint8_t apdu_len, const uint8_t *apdu);
+int gsm48_send_rr_app_info(struct msc_a *msc_a, uint8_t apdu_id, uint8_t apdu_len, const uint8_t *apdu);
int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_class);
-int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
- uint8_t power_command, uint8_t ho_ref);
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan, uint8_t power_command, uint8_t ho_ref);
int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg);
@@ -79,4 +74,12 @@ int gsm48_tch_rtp_create(struct gsm_trans *trans);
int gsm48_conn_sendmsg(struct msgb *msg, struct ran_conn *conn, struct gsm_trans *trans);
struct msgb *gsm48_create_mm_info(struct gsm_network *net);
+int gsm0408_rcv_cc(struct msc_a *msc_a, struct msgb *msg);
+int gsm0408_rcv_mm(struct msc_a *msc_a, struct msgb *msg);
+int gsm0408_rcv_rr(struct msc_a *msc_a, struct msgb *msg);
+
+int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type);
+
+int compl_l3_msg_is_r99(const struct msgb *msg);
+
#endif
diff --git a/include/osmocom/msc/gsm_04_11.h b/include/osmocom/msc/gsm_04_11.h
index 4297cdb4a..be8bff3c3 100644
--- a/include/osmocom/msc/gsm_04_11.h
+++ b/include/osmocom/msc/gsm_04_11.h
@@ -7,6 +7,7 @@
struct vlr_subscr;
struct ran_conn;
struct gsm_trans;
+struct msc_a;
#define UM_SAPI_SMS 3 /* See GSM 04.05/04.06 */
@@ -31,7 +32,7 @@ struct sms_deliver {
struct gsm_network;
struct msgb;
-int gsm0411_rcv_sms(struct ran_conn *conn, struct msgb *msg);
+int gsm0411_rcv_sms(struct msc_a *msc_a, struct msgb *msg);
struct gsm_sms *sms_alloc(void);
void sms_free(struct gsm_sms *sms);
@@ -46,7 +47,7 @@ int gsm411_send_rp_data(struct gsm_network *net, struct vlr_subscr *vsub,
size_t sm_rp_oa_len, const uint8_t *sm_rp_oa,
size_t sm_rp_ud_len, const uint8_t *sm_rp_ud);
-void gsm411_sapi_n_reject(struct ran_conn *conn);
+void gsm411_sapi_n_reject(struct msc_a *msc_a);
int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref);
int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref,
diff --git a/include/osmocom/msc/gsm_04_11_gsup.h b/include/osmocom/msc/gsm_04_11_gsup.h
index 969eabad2..4034f5e19 100644
--- a/include/osmocom/msc/gsm_04_11_gsup.h
+++ b/include/osmocom/msc/gsm_04_11_gsup.h
@@ -2,6 +2,7 @@
#include <stdint.h>
+struct gsup_client_mux;
struct osmo_gsup_message;
struct vlr_subscr;
struct gsm_trans;
@@ -10,11 +11,9 @@ struct msgb;
int gsm411_gsup_mo_ready_for_sm_req(struct gsm_trans *trans, uint8_t sm_rp_mr);
int gsm411_gsup_mo_fwd_sm_req(struct gsm_trans *trans, struct msgb *msg,
uint8_t sm_rp_mr, uint8_t *sm_rp_da, uint8_t sm_rp_da_len);
-int gsm411_gsup_mo_handler(struct vlr_subscr *vsub,
- struct osmo_gsup_message *gsup_msg);
int gsm411_gsup_mt_fwd_sm_res(struct gsm_trans *trans, uint8_t sm_rp_mr);
int gsm411_gsup_mt_fwd_sm_err(struct gsm_trans *trans,
uint8_t sm_rp_mr, uint8_t cause);
-int gsm411_gsup_mt_handler(struct vlr_subscr *vsub,
- struct osmo_gsup_message *gsup_msg);
+
+int gsm411_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg);
diff --git a/include/osmocom/msc/gsm_04_14.h b/include/osmocom/msc/gsm_04_14.h
index a6bafce90..3513c68bb 100644
--- a/include/osmocom/msc/gsm_04_14.h
+++ b/include/osmocom/msc/gsm_04_14.h
@@ -2,14 +2,16 @@
#include <osmocom/gsm/protocol/gsm_04_14.h>
-int gsm0414_tx_close_tch_loop_cmd(struct ran_conn *conn,
+struct msc_a;
+
+int gsm0414_tx_close_tch_loop_cmd(struct msc_a *msc_a,
enum gsm414_tch_loop_mode loop_mode);
-int gsm0414_tx_open_loop_cmd(struct ran_conn *conn);
-int gsm0414_tx_act_emmi_cmd(struct ran_conn *conn);
-int gsm0414_tx_test_interface(struct ran_conn *conn,
+int gsm0414_tx_open_loop_cmd(struct msc_a *msc_a);
+int gsm0414_tx_act_emmi_cmd(struct msc_a *msc_a);
+int gsm0414_tx_test_interface(struct msc_a *msc_a,
uint8_t tested_devs);
-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);
-int gsm0414_rcv_test(struct ran_conn *conn,
+int gsm0414_rcv_test(struct msc_a *msc_a,
struct msgb *msg);
diff --git a/include/osmocom/msc/gsm_04_80.h b/include/osmocom/msc/gsm_04_80.h
index b786dcc49..bb6573b26 100644
--- a/include/osmocom/msc/gsm_04_80.h
+++ b/include/osmocom/msc/gsm_04_80.h
@@ -2,16 +2,13 @@
#include <stdint.h>
-struct ran_conn;
+struct msc_a;
-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);
-int msc_send_ussd_notify(struct ran_conn *conn, int level,
- const char *text);
-int msc_send_ussd_release_complete(struct ran_conn *conn,
- uint8_t transaction_id);
-int msc_send_ussd_release_complete_cause(struct ran_conn *conn,
+int msc_send_ussd_notify(struct msc_a *msc_a, int level, const char *text);
+int msc_send_ussd_release_complete(struct msc_a *msc_a, uint8_t transaction_id);
+int msc_send_ussd_release_complete_cause(struct msc_a *msc_a,
uint8_t transaction_id,
uint8_t cause_loc, uint8_t cause_val);
diff --git a/include/osmocom/msc/gsm_09_11.h b/include/osmocom/msc/gsm_09_11.h
index 8fbe41be3..324befcd1 100644
--- a/include/osmocom/msc/gsm_09_11.h
+++ b/include/osmocom/msc/gsm_09_11.h
@@ -1,7 +1,9 @@
#pragma once
-#include <osmocom/core/msgb.h>
-#include <osmocom/gsm/gsup.h>
+struct msc_a;
+struct mgsb;
+struct gsup_client_mux;
+struct osmo_gsup_message;
-int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg);
-int gsm0911_gsup_handler(struct vlr_subscr *vsub, struct osmo_gsup_message *gsup);
+int gsm0911_rcv_nc_ss(struct msc_a *msc_a, struct msgb *msg);
+int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *msg);
diff --git a/include/osmocom/msc/gsm_data.h b/include/osmocom/msc/gsm_data.h
index 1a0d14463..42bb69a06 100644
--- a/include/osmocom/msc/gsm_data.h
+++ b/include/osmocom/msc/gsm_data.h
@@ -16,23 +16,17 @@
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/msc/msc_common.h>
+#include <osmocom/msc/neighbor_ident.h>
#include "gsm_data_shared.h"
-/* TS 48.008 DLCI containing DCCH/ACCH + SAPI */
-#define OMSC_LINKID_CB(__msgb) (__msgb)->cb[3]
-
-#include "../../bscconfig.h"
-#if BUILD_IU
-#include <osmocom/ranap/iu_client.h>
-#endif
-
/** annotations for msgb ownership */
#define __uses
struct mncc_sock_state;
struct vlr_instance;
struct vlr_subscr;
+struct gsup_client_mux;
#define tmsi_from_string(str) strtoul(str, NULL, 10)
@@ -144,6 +138,7 @@ struct gsm_network {
struct mncc_sock_state *mncc_state;
mncc_recv_cb_t mncc_recv;
struct llist_head upqueue;
+ struct osmo_tdef *mncc_tdefs;
/*
* TODO: Move the trans_list into the RAN connection and
* create a pending list for MT transactions. These exist before
@@ -171,9 +166,6 @@ struct gsm_network {
/* control interface */
struct ctrl_handle *ctrl;
- /* all active RAN connections. */
- struct llist_head ran_conns;
-
/* if override is nonzero, this timezone data is used for all MM
* contexts. */
/* TODO: in OsmoNITB, tz-override used to be BTS-specific. To enable
@@ -184,6 +176,7 @@ struct gsm_network {
/* MSC: GSUP server address of the HLR */
const char *gsup_server_addr_str;
uint16_t gsup_server_port;
+ struct gsup_client_mux *gcm;
struct vlr_instance *vlr;
@@ -196,28 +189,30 @@ struct gsm_network {
int ncss_guard_timeout;
struct {
+ struct osmo_tdef *tdefs;
struct mgcp_client_conf conf;
struct mgcp_client *client;
} mgw;
-#if BUILD_IU
struct {
/* CS7 instance id number (set via VTY) */
uint32_t cs7_instance;
- enum ranap_nsap_addr_enc rab_assign_addr_enc;
- struct osmo_sccp_instance *sccp;
+ enum nsap_addr_enc rab_assign_addr_enc;
+
+ struct sccp_ran_inst *sri;
} iu;
-#endif
struct {
/* CS7 instance id number (set via VTY) */
uint32_t cs7_instance;
- /* A list with the context information about
- * all BSCs we have connections with */
- struct llist_head bscs;
- struct osmo_sccp_instance *sccp;
+
+ struct sccp_ran_inst *sri;
} a;
+ /* A list of neighbor BSCs. This list is defined statically via VTY and does not
+ * necessarily correspond to BSCs attached to the A interface at a given moment. */
+ struct neighbor_ident_list *neighbor_list;
+
struct {
/* MSISDN to which to route MO emergency calls */
char *route_to_msisdn;
@@ -228,6 +223,14 @@ struct gsm_network {
* If no name is set, the IPA Serial Number will be the same as the Unit Name,
* and will be of the form 'MSC-00-00-00-00-00-00' */
char *msc_ipa_name;
+
+ struct llist_head neighbor_ident_list;
+
+ struct {
+ uint64_t range_start;
+ uint64_t range_end;
+ uint64_t next;
+ } handover_number;
};
struct osmo_esme;
diff --git a/include/osmocom/msc/gsm_data_shared.h b/include/osmocom/msc/gsm_data_shared.h
index 732607bc1..511d6bca3 100644
--- a/include/osmocom/msc/gsm_data_shared.h
+++ b/include/osmocom/msc/gsm_data_shared.h
@@ -31,10 +31,4 @@ enum gsm_hooks {
GSM_HOOK_RR_SECURITY,
};
-enum gsm_paging_event {
- GSM_PAGING_SUCCEEDED,
- GSM_PAGING_EXPIRED,
- GSM_PAGING_BUSY,
-};
-
#endif
diff --git a/include/osmocom/msc/gsm_subscriber.h b/include/osmocom/msc/gsm_subscriber.h
index f848ac850..31eca6b0f 100644
--- a/include/osmocom/msc/gsm_subscriber.h
+++ b/include/osmocom/msc/gsm_subscriber.h
@@ -12,40 +12,4 @@
struct ran_conn;
struct msgb;
-typedef int gsm_cbfn(unsigned int hooknum, unsigned int event, struct msgb *msg,
- void *data, void *param);
-
-/*
- * Struct for pending channel requests. This is managed in the
- * llist_head requests of each subscriber. The reference counting
- * should work in such a way that a subscriber with a pending request
- * remains in memory.
- */
-struct subscr_request {
- struct llist_head entry;
-
- /* human readable label to be able to log pending request kinds */
- const char *label;
-
- /* the callback data */
- gsm_cbfn *cbfn;
- void *param;
-};
-
-/*
- * Paging handling with authentication
- */
-struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub,
- gsm_cbfn *cbfn, void *param,
- const char *label,
- enum sgsap_service_ind serv_ind);
-void subscr_remove_request(struct subscr_request *req);
-
-void subscr_paging_cancel(struct vlr_subscr *vsub, enum gsm_paging_event event);
-int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *data, void *param);
-
-/* Find an allocated channel for a specified subscriber */
-struct ran_conn *connection_for_subscr(struct vlr_subscr *vsub);
-
#endif /* _GSM_SUBSCR_H */
diff --git a/include/osmocom/msc/gsup_client_mux.h b/include/osmocom/msc/gsup_client_mux.h
new file mode 100644
index 000000000..07f17c260
--- /dev/null
+++ b/include/osmocom/msc/gsup_client_mux.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/msc/gsup_client_mux.h>
+
+struct gsup_client_mux;
+struct ipaccess_unit;
+
+struct gsup_client_mux_rx_cb {
+ int (* func )(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg);
+ void *data;
+};
+
+/* A GSUP client shared between code paths for various GSUP Message Classes.
+ * The main task is to dispatch GSUP messages to code paths corresponding to the respective Message Class, i.e.
+ * subscriber management, SMS, SS/USSD and inter-MSC messaging.
+ * If a GSUP Message Class IE is present in the message, the received message is dispatched directly to the rx_cb entry
+ * for that Message Class. Otherwise, the Message Class is determined by a switch() on the Message Type.*/
+struct gsup_client_mux {
+ struct osmo_gsup_client *gsup_client;
+
+ /* Target clients by enum osmo_gsup_message_class */
+ struct gsup_client_mux_rx_cb rx_cb[OSMO_GSUP_MESSAGE_CLASS_ARRAYSIZE];
+};
+
+struct gsup_client_mux *gsup_client_mux_alloc(void *talloc_ctx);
+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);
+
+int gsup_client_mux_tx(struct gsup_client_mux *gcm, const struct osmo_gsup_message *gsup_msg);
+void gsup_client_mux_tx_error_reply(struct gsup_client_mux *gcm, const struct osmo_gsup_message *gsup_orig,
+ enum gsm48_gmm_cause cause);
+
+int gsup_client_mux_rx(struct osmo_gsup_client *gsup_client, struct msgb *msg);
diff --git a/include/osmocom/msc/iu_dummy.h b/include/osmocom/msc/iu_dummy.h
deleted file mode 100644
index 01a8aa608..000000000
--- a/include/osmocom/msc/iu_dummy.h
+++ /dev/null
@@ -1,50 +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 <stdint.h>
-#include <stdbool.h>
-
-#include <osmocom/core/linuxlist.h>
-
-struct msgb;
-struct RANAP_Cause;
-struct osmo_auth_vector;
-
-struct ranap_ue_conn_ctx {
- struct llist_head list;
- uint32_t conn_id;
-};
-
-int ranap_iu_tx(struct msgb *msg, uint8_t sapi);
-int ranap_iu_tx_sec_mode_cmd(struct ranap_ue_conn_ctx *uectx, struct osmo_auth_vector *vec,
- int send_ck);
-int ranap_iu_page_cs(const char *imsi, const uint32_t *tmsi, uint16_t lac);
-int ranap_iu_page_ps(const char *imsi, const uint32_t *ptmsi, uint16_t lac, uint8_t rac);
-struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id, uint32_t rtp_ip,
- uint16_t rtp_port,
- bool use_x213_nsap);
-int ranap_iu_rab_act(struct ranap_ue_conn_ctx *ue_ctx, struct msgb *msg);
-int ranap_iu_tx_common_id(struct ranap_ue_conn_ctx *uectx, const char *imsi);
-int ranap_iu_tx_release(struct ranap_ue_conn_ctx *ctx, const struct RANAP_Cause *cause);
diff --git a/include/osmocom/msc/iucs.h b/include/osmocom/msc/iucs.h
deleted file mode 100644
index 302edc0e6..000000000
--- a/include/osmocom/msc/iucs.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include <osmocom/msc/transaction.h>
-
-struct ranap_ue_conn_ctx;
-
-int gsm0408_rcvmsg_iucs(struct gsm_network *network, struct msgb *msg,
- uint16_t *lac);
-
-struct ran_conn *ran_conn_lookup_iu(struct gsm_network *network,
- struct ranap_ue_conn_ctx *ue);
-int iu_rab_act_cs(struct gsm_trans *trans);
-
-uint32_t iu_get_conn_id(const struct ranap_ue_conn_ctx *ue);
diff --git a/include/osmocom/msc/iucs_ranap.h b/include/osmocom/msc/iucs_ranap.h
deleted file mode 100644
index c2ff5f90e..000000000
--- a/include/osmocom/msc/iucs_ranap.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-struct gsm_network;
-struct ranap_ue_conn_ctx;
-
-int iucs_rx_ranap_event(struct gsm_network *network,
- struct ranap_ue_conn_ctx *ue_ctx, int type, void *data);
diff --git a/include/osmocom/msc/mncc.h b/include/osmocom/msc/mncc.h
index a9be0048c..28ee9b339 100644
--- a/include/osmocom/msc/mncc.h
+++ b/include/osmocom/msc/mncc.h
@@ -1,4 +1,4 @@
-/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
* 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
@@ -31,6 +31,7 @@
struct gsm_network;
struct msgb;
+struct gsm0808_channel_type;
/* One end of a call */
@@ -196,6 +197,15 @@ struct gsm_mncc_bridge {
uint32_t callref[2];
};
+union mncc_msg {
+ uint32_t msg_type;
+ struct gsm_mncc signal;
+ struct gsm_mncc_hello hello;
+ struct gsm_data_frame data_frame;
+ struct gsm_mncc_rtp rtp;
+ struct gsm_mncc_bridge bridge;
+};
+
const char *get_mncc_name(int value);
void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg);
@@ -217,4 +227,6 @@ int mncc_sock_init(struct gsm_network *net, const char *sock_path);
int mncc_prim_check(const struct gsm_mncc *mncc_prim, unsigned int len);
+int mncc_bearer_cap_to_channel_type(struct gsm0808_channel_type *ct, const struct gsm_mncc_bearer_cap *bc);
+
#endif
diff --git a/include/osmocom/msc/mncc_call.h b/include/osmocom/msc/mncc_call.h
new file mode 100644
index 000000000..ad0f0f841
--- /dev/null
+++ b/include/osmocom/msc/mncc_call.h
@@ -0,0 +1,140 @@
+/* Handle an MNCC managed call (external MNCC). */
+/*
+ * (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/>.
+ */
+#pragma once
+
+#include <osmocom/msc/mncc.h>
+#include <osmocom/msc/mncc_call.h>
+
+struct osmo_fsm_inst;
+struct rtp_stream;
+
+#define LOG_MNCC_CALL(MNCC, LEVEL, FMT, ARGS...) \
+ LOGPFSML((MNCC) ? (MNCC)->fi : NULL, LEVEL, FMT, ##ARGS)
+
+enum mncc_call_fsm_event {
+ /* An MNCC message was received from the MNCC socket. The data argument is a const union mncc_msg* pointing at
+ * the message contents. */
+ MNCC_CALL_EV_RX_MNCC_MSG,
+
+ /* The user has invoked mncc_call_outgoing_start(); this event exists to ensure that the FSM is in a state that
+ * allows starting a new outgoing call. */
+ MNCC_CALL_EV_OUTGOING_START,
+ /* The MNCC server has sent an MNCC_ALERT_REQ. */
+ MNCC_CALL_EV_OUTGOING_ALERTING,
+ /* The MNCC server has confirmed call setup with an MNCC_SETUP_RSP, we have sent an MNCC_SETUP_COMPL_IND. */
+ MNCC_CALL_EV_OUTGOING_SETUP_COMPLETE,
+
+ /* The user has invoked mncc_call_incoming_start(); this event exists to ensure that the FSM is in a state that
+ * allows starting a new incoming call. */
+ MNCC_CALL_EV_INCOMING_START,
+ /* MNCC server sent an MNCC_SETUP_REQ */
+ MNCC_CALL_EV_INCOMING_SETUP,
+ /* MNCC server confirmed call setup with an MNCC_SETUP_COMPL_REQ */
+ MNCC_CALL_EV_INCOMING_SETUP_COMPLETE,
+
+ /* MNCC server requests call release (Rx MNCC_DISC_REQ) */
+ MNCC_CALL_EV_CN_RELEASE,
+ /* osmo-msc should request call release (Tx MNCC_DISC_IND) */
+ MNCC_CALL_EV_MS_RELEASE,
+};
+
+/* The typical progression of outgoing and incoming calls via MNCC is shown by doc/sequence_charts/mncc_call_fsm.msc */
+enum mncc_call_fsm_state {
+ MNCC_CALL_ST_NOT_STARTED = 0,
+
+ MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING,
+ MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE,
+
+ MNCC_CALL_ST_INCOMING_WAIT_COMPLETE,
+
+ MNCC_CALL_ST_TALKING,
+
+ MNCC_CALL_ST_WAIT_RELEASE_ACK,
+};
+
+struct mncc_call_incoming_req {
+ bool bearer_cap_present;
+ struct gsm_mncc_bearer_cap bearer_cap;
+
+ bool cccap_present;
+ struct gsm_mncc_cccap cccap;
+
+ struct gsm_mncc setup_req_msg;
+};
+
+struct mncc_call;
+typedef void (* mncc_call_message_cb_t )(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg, void *data);
+
+struct mncc_call {
+ struct llist_head entry;
+
+ struct osmo_fsm_inst *fi;
+ struct vlr_subscr *vsub;
+ struct gsm_network *net;
+
+ /* Details originally passed to mncc_call_outgoing_start(), if any. */
+ struct gsm_mncc outgoing_req;
+
+ uint32_t callref;
+ bool remote_msisdn_present;
+ struct gsm_mncc_number remote_msisdn;
+ bool local_msisdn_present;
+ struct gsm_mncc_number local_msisdn;
+ struct rtp_stream *rtps;
+ bool received_rtp_create;
+
+ mncc_call_message_cb_t message_cb;
+ void *forward_cb_data;
+
+ /* Event to dispatch to the FSM inst parent when the call is complete. Omit event dispatch when negative. See
+ * mncc_call_alloc()'s arg of same name. */
+ int parent_event_call_setup_complete;
+};
+
+void mncc_call_fsm_init(struct gsm_network *net);
+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);
+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);
+
+int mncc_call_outgoing_start(struct mncc_call *mncc_call, const struct gsm_mncc *outgoing_req);
+
+int mncc_call_incoming_start(struct mncc_call *mncc_call, const struct mncc_call_incoming_req *incoming_req);
+int mncc_call_incoming_tx_setup_cnf(struct mncc_call *mncc_call, const struct gsm_mncc_number *connected_number);
+
+int mncc_call_set_rtp_stream(struct mncc_call *mncc_call, struct rtp_stream *rtps);
+void mncc_call_detach_rtp_stream(struct mncc_call *mncc_call);
+
+void mncc_call_rx(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg);
+int mncc_call_tx(struct mncc_call *mncc_call, union mncc_msg *mncc_msg);
+int mncc_call_tx_msgt(struct mncc_call *mncc_call, uint32_t msg_type);
+
+struct mncc_call *mncc_call_find_by_callref(uint32_t callref);
+
+void mncc_call_release(struct mncc_call *mncc_call);
diff --git a/include/osmocom/msc/msc_a.h b/include/osmocom/msc/msc_a.h
new file mode 100644
index 000000000..c732695a1
--- /dev/null
+++ b/include/osmocom/msc/msc_a.h
@@ -0,0 +1,215 @@
+/* MSC-A role: main subscriber management */
+/*
+ * (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/>.
+ */
+#pragma once
+
+#include <osmocom/core/use_count.h>
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm23003.h>
+
+#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/msc_common.h>
+#include <osmocom/msc/msc_ho.h>
+#include <osmocom/msc/neighbor_ident.h>
+
+struct ran_infra;
+
+#define MSC_A_USE_LOCATION_UPDATING "lu"
+#define MSC_A_USE_CM_SERVICE_CC "cm_service_cc"
+#define MSC_A_USE_CM_SERVICE_SMS "cm_service_sms"
+#define MSC_A_USE_CM_SERVICE_SS "cm_service_ss"
+#define MSC_A_USE_PAGING_RESPONSE "paging-response"
+#define MSC_A_USE_CC "cc"
+#define MSC_A_USE_SMS "sms"
+#define MSC_A_USE_NC_SS "nc_ss"
+#define MSC_A_USE_SILENT_CALL "silent_call"
+
+/* These are macros to use the source file:line information from the caller in a trivial way */
+#define msc_a_get(msc_a, use) \
+ OSMO_ASSERT(osmo_use_count_get_put(&msc_a->use_count, use, 1) == 0)
+#define msc_a_put(msc_a, use) \
+ OSMO_ASSERT(osmo_use_count_get_put(&msc_a->use_count, use, -1) == 0)
+#define msc_a_put_all(msc_a, use) do { \
+ int32_t has_count = osmo_use_count_by(&msc_a->use_count, use); \
+ if (has_count) \
+ OSMO_ASSERT(osmo_use_count_get_put(&msc_a->use_count, use, -has_count) == 0); \
+ } while(0)
+
+
+enum msc_a_action_on_classmark_update_type {
+ MSC_A_CLASSMARK_UPDATE_NOT_EXPECTED = 0,
+ MSC_A_CLASSMARK_UPDATE_THEN_CIPHERING,
+};
+
+/* A Classmark Update might be required for various tasks. At the time of writing, the only use case is to determine A5
+ * capabilities for choosing a ciphering algorithm. This structure anticipates other Classmark Update use cases to be
+ * added in the future. */
+struct msc_a_action_on_classmark_update {
+ enum msc_a_action_on_classmark_update_type type;
+ union {
+ /* State required to resume Ciphering after the Classmark Request / Classmark Update is complete. */
+ struct {
+ bool umts_aka;
+ bool retrieve_imeisv;
+ } ciphering;
+
+ /* Add more use cases here... */
+ };
+};
+
+struct msc_a {
+ /* struct msc_role_common must remain at start */
+ struct msc_role_common c;
+ enum complete_layer3_type complete_layer3_type;
+ struct osmo_cell_global_id via_cell;
+
+ /* Temporary storage for Classmark Information for times when a connection has no VLR subscriber
+ * associated yet. It will get copied to the VLR subscriber upon msc_vlr_subscr_assoc(). */
+ struct osmo_gsm48_classmark temporary_classmark;
+
+ /* See handling of E_MSC_A_CLASSMARK_UPDATE */
+ struct msc_a_action_on_classmark_update action_on_classmark_update;
+ uint32_t state_before_classmark_update;
+
+ /* After Ciphering Mode Complete on GERAN, this reflects the chosen ciphering algorithm and key */
+ struct geran_encr geran_encr;
+
+ /* N(SD) expected in the received frame, per flow (TS 24.007 11.2.3.2.3.2.2) */
+ uint8_t n_sd_next[4];
+
+ /* Call control and MSC-A side of RTP switching. Without inter-MSC handover involved, this manages all of the
+ * MGW and RTP switching; after an inter-MSC handover, the RAN-side of this is redirected via another MNCC
+ * connection to the Handover MSISDN, and a remote MSC-I role takes over RTP switching to the remote BSS.
+ *
+ * Without / before inter-MSC HO:
+ *
+ * BSS [MSC-I MSC-A] MNCC to PBX
+ * <--RTP---------> <--RTP-->
+ *
+ * After inter-MSC HO:
+ *
+ * BSS [MSC-I MSC-A] MNCC to PBX MSC-I BSS-B
+ * /--> <--RTP-->
+ * \-------RTP--> (ISUP) <--RTP--> <--RTP-->
+ */
+ struct {
+ /* All of the RTP stream handling */
+ struct call_leg *call_leg;
+ struct mncc_call *mncc_forwarding_to_remote_ran;
+
+ /* There may be up to 7 incoming calls for this subscriber. This is the currently serviced voice call,
+ * as in, the other person the subscriber is currently talking to. */
+ struct gsm_trans *active_trans;
+ } cc;
+
+ struct msc_ho_state ho;
+
+ struct osmo_use_count use_count;
+ struct osmo_use_count_entry use_count_buf[8];
+ int32_t max_total_use_count;
+};
+
+osmo_static_assert(offsetof(struct msc_a, c) == 0, msc_role_common_first_member_of_msc_a);
+
+struct msc_a_ran_dec_data {
+ enum msc_role from_role;
+ const struct an_apdu *an_apdu;
+ const struct ran_msg *ran_dec;
+};
+
+#define LOG_MSC_A(MSC_A, LEVEL, FMT, ARGS ...) \
+ LOG_MSC_A_CAT(MSC_A, (MSC_A) ? (MSC_A)->c.ran->log_subsys : DMSC, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_A_CAT(MSC_A, SUBSYS, LEVEL, FMT, ARGS ...) \
+ LOGPFSMSL((MSC_A) ? (MSC_A)->c.fi : NULL, SUBSYS, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_A_CAT_SRC(MSC_A, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ARGS ...) \
+ LOGPFSMSLSRC((MSC_A) ? (MSC_A)->c.fi : NULL, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ## ARGS)
+
+enum msc_a_states {
+ MSC_A_ST_VALIDATE_L3,
+ MSC_A_ST_AUTH_CIPH,
+ MSC_A_ST_WAIT_CLASSMARK_UPDATE,
+ MSC_A_ST_AUTHENTICATED,
+ MSC_A_ST_COMMUNICATING,
+ MSC_A_ST_RELEASING,
+ MSC_A_ST_RELEASED,
+};
+
+struct msc_a *msc_a_alloc(struct msub *msub, struct ran_infra *ran);
+
+int msc_a_classmark_request_then_cipher_mode_cmd(struct msc_a *msc_a, bool umts_aka, bool retrieve_imeisv);
+
+bool msc_a_is_establishing_auth_ciph(const struct msc_a *msc_a);
+bool msc_a_is_accepted(const struct msc_a *msc_a);
+bool msc_a_in_release(struct msc_a *msc_a);
+
+struct gsm_network *msc_a_net(const struct msc_a *msc_a);
+struct vlr_subscr *msc_a_vsub(const struct msc_a *msc_a);
+struct msc_i *msc_a_msc_i(const struct msc_a *msc_a);
+struct msc_t *msc_a_msc_t(const struct msc_a *msc_a);
+
+struct msc_a *msc_a_for_vsub(const struct vlr_subscr *vsub, bool valid_conn_only);
+
+void msc_a_pending_cm_service_req_add(struct msc_a *msc_a, enum osmo_cm_service_type type);
+unsigned int msc_a_pending_cm_service_req_count(struct msc_a *msc_a, enum osmo_cm_service_type type);
+void msc_a_pending_cm_service_req_del(struct msc_a *msc_a, enum osmo_cm_service_type type);
+
+#define msc_a_ran_down(A,B,C) \
+ _msc_a_ran_down(A,B,C, __FILE__, __LINE__)
+int _msc_a_ran_down(struct msc_a *msc_a, enum msc_role to_role, const struct ran_msg *ran_enc_msg,
+ const char *file, int line);
+#define msc_a_msg_down(A,B,C,D) \
+ _msc_a_msg_down(A,B,C,D, __FILE__, __LINE__)
+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_enc_msg,
+ const char *file, int line);
+
+int msc_a_tx_dtap_to_i(struct msc_a *msc_a, struct msgb *dtap);
+int msc_a_tx_common_id(struct msc_a *msc_a);
+int msc_a_tx_mm_serv_ack(struct msc_a *msc_a);
+int msc_a_tx_mm_serv_rej(struct msc_a *msc_a, enum gsm48_reject_value value);
+
+int msc_a_up_l3(struct msc_a *msc_a, struct msgb *msg);
+
+void msc_a_up_ciph_res(struct msc_a *msc_a, bool success, const char *imeisv);
+
+bool msc_a_is_accepted(const struct msc_a *msc_a);
+bool msc_a_is_establishing_auth_ciph(const struct msc_a *msc_a);
+
+int msc_a_try_call_assignment(struct gsm_trans *cc_trans);
+
+const char *msc_a_cm_service_type_to_use(enum osmo_cm_service_type cm_service_type);
+
+void msc_a_release_cn(struct msc_a *msc_a);
+void msc_a_release_mo(struct msc_a *msc_a, enum gsm48_gsm_cause gsm_cause);
+
+int msc_a_ran_decode_cb(struct osmo_fsm_inst *msc_a_fi, void *data, const struct ran_msg *msg);
+
+int msc_a_vlr_set_cipher_mode(void *_msc_a, bool umts_aka, bool retrieve_imeisv);
+
+struct msgb *msc_a_ran_encode(struct msc_a *msc_a, const struct ran_msg *ran_enc_msg);
+
+void msc_a_update_id(struct msc_a *msc_a);
diff --git a/include/osmocom/msc/msc_a_remote.h b/include/osmocom/msc/msc_a_remote.h
new file mode 100644
index 000000000..db7f50773
--- /dev/null
+++ b/include/osmocom/msc/msc_a_remote.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#define LOG_MSC_A_REMOTE(MSC_A_REMOTE, LEVEL, FMT, ARGS ...) \
+ LOG_MSC_A_REMOTE_CAT(MSC_A_REMOTE, (MSC_A_REMOTE) ? (MSC_A_REMOTE)->c.ran->log_subsys : DMSC, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_A_REMOTE_CAT(MSC_A_REMOTE, SUBSYS, LEVEL, FMT, ARGS ...) \
+ LOGPFSMSL((MSC_A_REMOTE) ? (MSC_A_REMOTE)->c.fi : NULL, SUBSYS, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_A_REMOTE_CAT_SRC(MSC_A_REMOTE, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ARGS ...) \
+ LOGPFSMSLSRC((MSC_A_REMOTE) ? (MSC_A_REMOTE)->c.fi : NULL, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ## ARGS)
+
+struct msub;
+struct ran_infra;
+
+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);
+
+int msc_a_remote_assign_handover_number(struct msc_a *msc_a);
+struct msc_a *msc_a_remote_find_by_handover_number(const char *handover_number);
diff --git a/include/osmocom/msc/msc_common.h b/include/osmocom/msc/msc_common.h
index 3ca34692d..78337f764 100644
--- a/include/osmocom/msc/msc_common.h
+++ b/include/osmocom/msc/msc_common.h
@@ -1,5 +1,8 @@
#pragma once
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0808.h>
+
struct msgb;
struct gsm_network;
struct vlr_subscr;
@@ -7,17 +10,51 @@ struct vlr_subscr;
#define MSC_HLR_REMOTE_IP_DEFAULT "127.0.0.1"
#define MSC_HLR_REMOTE_PORT_DEFAULT OSMO_GSUP_PORT
+/* TS 48.008 DLCI containing DCCH/ACCH + SAPI */
+#define OMSC_LINKID_CB(__msgb) (__msgb)->cb[3]
+
enum nsap_addr_enc {
NSAP_ADDR_ENC_X213,
NSAP_ADDR_ENC_V4RAW,
};
+#define MAX_A5_KEY_LEN (128/8)
+
+struct geran_encr {
+ /*! alg_id is in encoded format:
+ * alg_id == 1 means A5/0 i.e. no encryption, alg_id == 4 means A5/3.
+ * alg_id == 0 means no such IE was present. */
+ uint8_t alg_id;
+ uint8_t key_len;
+ uint8_t key[MAX_A5_KEY_LEN];
+};
+
+enum complete_layer3_type {
+ COMPLETE_LAYER3_NONE,
+ COMPLETE_LAYER3_LU,
+ COMPLETE_LAYER3_CM_SERVICE_REQ,
+ COMPLETE_LAYER3_PAGING_RESP,
+};
+
+extern const struct value_string complete_layer3_type_names[];
+static inline const char *complete_layer3_type_name(enum complete_layer3_type val)
+{
+ return get_value_string(complete_layer3_type_names, val);
+}
+
+struct cell_ids_entry {
+ struct llist_head entry;
+ struct gsm0808_cell_id_list2 cell_ids;
+};
+
typedef int (*mncc_recv_cb_t)(struct gsm_network *, struct msgb *);
struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv);
void gsm_network_set_mncc_sock_path(struct gsm_network *net, const char *mncc_sock_path);
+extern const struct vlr_ops msc_vlr_ops;
int msc_vlr_alloc(struct gsm_network *net);
int msc_vlr_start(struct gsm_network *net);
+int msc_gsup_client_start(struct gsm_network *net);
-void msc_stop_paging(struct vlr_subscr *vsub);
+uint32_t msc_cc_next_outgoing_callref();
diff --git a/include/osmocom/msc/msc_ho.h b/include/osmocom/msc/msc_ho.h
new file mode 100644
index 000000000..99956f1e6
--- /dev/null
+++ b/include/osmocom/msc/msc_ho.h
@@ -0,0 +1,104 @@
+/* MSC Handover API */
+/*
+ * (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/>.
+ */
+
+#pragma once
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/sockaddr_str.h>
+
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+#include <osmocom/msc/neighbor_ident.h>
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/mncc_call.h>
+
+
+struct gsm0808_handover_required;
+
+struct msc_a;
+struct ran_dec_handover_required;
+
+#define LOG_HO(msc_a, level, fmt, args...) \
+ LOGPFSML((msc_a)? ((msc_a)->ho.fi ? : (msc_a)->c.fi) : NULL, \
+ level, "%s" fmt, (msc_a->ho.fi ? "" : "HO: "), ##args)
+
+enum msc_ho_fsm_state {
+ MSC_HO_ST_REQUIRED,
+ MSC_HO_ST_WAIT_REQUEST_ACK,
+ MSC_HO_ST_WAIT_COMPLETE,
+};
+
+enum msc_ho_fsm_event {
+ MSC_HO_EV_RX_REQUEST_ACK,
+ MSC_HO_EV_RX_DETECT,
+ MSC_HO_EV_RX_COMPLETE,
+ MSC_HO_EV_RX_FAILURE,
+ MSC_HO_EV_MNCC_FORWARDING_COMPLETE,
+ MSC_HO_EV_MNCC_FORWARDING_FAILED,
+};
+
+struct msc_ho_state {
+ struct osmo_fsm_inst *fi;
+ struct ran_handover_required info;
+ unsigned int next_cil_idx;
+ bool subsequent_ho;
+ bool ready_to_switch_rtp;
+ bool rtp_switched_to_new_cell;
+
+ struct {
+ enum osmo_rat_type ran_type;
+ struct gsm0808_cell_id cid;
+ struct osmo_cell_global_id cgi;
+ enum msc_neighbor_type type;
+ union {
+ struct ran_peer *ran_peer;
+ const char *msc_ipa_name;
+ };
+
+ /* The RTP address from Handover Request Acknowledge.
+ * Might be from AoIP Transport Layer Address from a BSC RAN peer,
+ * or from MNCC forwarding for inter-MSC handover. */
+ struct osmo_sockaddr_str ran_remote_rtp;
+ /* The codec from Handover Request Acknowledge. */
+ bool codec_present;
+ enum mgcp_codecs codec;
+
+ /* Inter-MSC voice forwarding via MNCC, to the remote MSC. The Prepare Handover Response sent us the
+ * Handover Number the remote MSC assigned. This is a call to that Handover Number, via PBX.
+ * (NULL if not an inter-MSC Handover) */
+ struct mncc_call *mncc_forwarding_to_remote_ran;
+ } new_cell;
+
+ struct {
+ /* Saved RTP IP:port and codec in case we need to roll back */
+ struct osmo_sockaddr_str ran_remote_rtp;
+ enum mgcp_codecs codec;
+ } old_cell;
+};
+
+void msc_ho_start(struct msc_a *msc_a, const struct ran_handover_required *ho_req);
+
+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);
diff --git a/include/osmocom/msc/msc_i.h b/include/osmocom/msc/msc_i.h
new file mode 100644
index 000000000..a2a5fb133
--- /dev/null
+++ b/include/osmocom/msc/msc_i.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/mncc.h>
+
+#include <osmocom/msc/msc_roles.h>
+
+struct ran_infra;
+struct mncc_call;
+
+#define LOG_MSC_I(MSC_I, LEVEL, FMT, ARGS ...) \
+ LOG_MSC_I_CAT(MSC_I, (MSC_I) ? (MSC_I)->c.ran->log_subsys : DMSC, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_I_CAT(MSC_I, SUBSYS, LEVEL, FMT, ARGS ...) \
+ LOGPFSMSL((MSC_I) ? (MSC_I)->c.fi : NULL, SUBSYS, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_I_CAT_SRC(MSC_I, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ARGS ...) \
+ LOGPFSMSLSRC((MSC_I) ? (MSC_I)->c.fi : NULL, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ## ARGS)
+
+struct msc_i {
+ /* struct msc_role_common must remain at start */
+ struct msc_role_common c;
+ struct ran_conn *ran_conn;
+
+ struct {
+ struct call_leg *call_leg;
+ struct mncc_call *mncc_forwarding_to_remote_cn;
+ } inter_msc;
+};
+
+osmo_static_assert(offsetof(struct msc_i, c) == 0, msc_role_common_first_member_of_msc_i);
+
+enum msc_i_state {
+ MSC_I_ST_READY,
+ MSC_I_ST_CLEARING,
+ MSC_I_ST_CLEARED,
+};
+
+struct msc_i *msc_i_alloc(struct msub *msub, struct ran_infra *ran);
+void msc_i_set_ran_conn(struct msc_i *msc_i, struct ran_conn *ran_conn);
+
+void msc_i_clear(struct msc_i *msc_i);
+void msc_i_cleared(struct msc_i *msc_i);
+
+int msc_i_down_l2(struct msc_i *msc_i, struct msgb *l2);
+
+struct gsm_network *msc_i_net(const struct msc_i *msc_i);
+struct vlr_subscr *msc_i_vsub(const struct msc_i *msc_i);
diff --git a/include/osmocom/msc/msc_i_remote.h b/include/osmocom/msc/msc_i_remote.h
new file mode 100644
index 000000000..526d76f86
--- /dev/null
+++ b/include/osmocom/msc/msc_i_remote.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#define LOG_MSC_I_REMOTE(MSC_I_REMOTE, LEVEL, FMT, ARGS ...) \
+ LOG_MSC_I_REMOTE_CAT(MSC_I_REMOTE, (MSC_I_REMOTE) ? (MSC_I_REMOTE)->c.ran->log_subsys : DMSC, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_I_REMOTE_CAT(MSC_I_REMOTE, SUBSYS, LEVEL, FMT, ARGS ...) \
+ LOGPFSMSL((MSC_I_REMOTE) ? (MSC_I_REMOTE)->c.fi : NULL, SUBSYS, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_I_REMOTE_CAT_SRC(MSC_I_REMOTE, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ARGS ...) \
+ LOGPFSMSLSRC((MSC_I_REMOTE) ? (MSC_I_REMOTE)->c.fi : NULL, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ## ARGS)
+
+struct msub;
+struct ran_infra;
+struct e_link;
+
+struct msc_i *msc_i_remote_alloc(struct msub *msub, struct ran_infra *ran, struct e_link *e);
diff --git a/include/osmocom/msc/msc_ifaces.h b/include/osmocom/msc/msc_ifaces.h
deleted file mode 100644
index 94423caa5..000000000
--- a/include/osmocom/msc/msc_ifaces.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#pragma once
-
-#include <osmocom/core/msgb.h>
-#include <osmocom/msc/gsm_data.h>
-#include <osmocom/msc/transaction.h>
-
-/* These are the interfaces of the MSC layer towards (from?) the BSC and RNC,
- * i.e. in the direction towards the mobile device (MS aka UE).
- *
- * 2G will use the A-interface,
- * 3G aka UMTS will use the Iu-interface (for the MSC, it's IuCS).
- *
- * To allow linking parts of the MSC code without having to include entire
- * infrastructures of external libraries, the core transmitting and receiving
- * functions are left unimplemented. For example, a unit test does not need to
- * link against external ASN1 libraries if it is never going to encode actual
- * outgoing messages. It is up to each building scope to implement real world
- * functions or to plug mere dummy implementations.
- *
- * For example, msc_tx_dtap(conn, msg), depending on conn->via_iface, will call
- * either iu_tx() or a_tx() [note: at time of writing, the A-interface is not
- * yet implemented]. When you try to link against libmsc, you will find that
- * the compiler complains about an undefined reference to iu_tx(). If you,
- * however, link against libiu as well as the osmo-iuh libs (etc.), iu_tx() is
- * available. A unit test may instead simply implement a dummy iu_tx() function
- * and not link against osmo-iuh, see tests/libiudummy/.
- */
-
-/* Each main linkage must implement this function (see comment above). */
-extern int iu_tx(struct msgb *msg, uint8_t sapi);
-
-int msc_tx_dtap(struct ran_conn *conn,
- struct msgb *msg);
-
-int msc_gsm48_tx_mm_serv_ack(struct ran_conn *conn);
-int msc_gsm48_tx_mm_serv_rej(struct ran_conn *conn,
- enum gsm48_reject_value value);
-
-int msc_tx_common_id(struct ran_conn *conn);
diff --git a/include/osmocom/msc/msc_mgcp.h b/include/osmocom/msc/msc_mgcp.h
deleted file mode 100644
index 304e96706..000000000
--- a/include/osmocom/msc/msc_mgcp.h
+++ /dev/null
@@ -1,65 +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/>.
- *
- */
-
-#pragma once
-
-#include <osmocom/mgcp_client/mgcp_client.h>
-#include <osmocom/msc/gsm_data.h>
-
-struct ran_conn;
-
-/* MGCP state handler context. This context information stores all information
- * to handle the direction of the RTP streams via MGCP. There is one instance
- * of this context struct per RAN connection.
- * (see also struct ran_conn) */
-struct mgcp_ctx {
- /* FSM instance, which handles the connection switching procedure */
- struct osmo_fsm_inst *fsm;
-
- /* RTP endpoint string. This string identifies the endpoint
- * on the MGW on which the RAN and CN connection is created. This
- * endpoint number is assigned by the MGW. */
- char rtp_endpoint[MGCP_ENDPOINT_MAXLEN];
-
- /* Call id of the current call. Will be derived from the callref
- * of the transaction that is valid during the first CRCX. (The
- * callref may change throughout the call) */
- unsigned int call_id;
-
- /* Set to true, when the context information is no longer needed */
- bool free_ctx;
-
- /* RTP connection identifiers */
- char conn_id_ran[MGCP_CONN_ID_LENGTH];
- char conn_id_cn[MGCP_CONN_ID_LENGTH];
-
- /* Copy of the pointer and the data with context information
- * needed to process the AoIP and MGCP requests (system data) */
- struct mgcp_client *mgcp;
- struct gsm_trans *trans;
- mgcp_trans_id_t mgw_pending_trans;
-};
-
-int msc_mgcp_try_call_assignment(struct gsm_trans *trans);
-int msc_mgcp_call_assignment(struct gsm_trans *trans);
-int msc_mgcp_ass_complete(struct ran_conn *conn, uint16_t port, char *addr);
-int msc_mgcp_ass_fail(struct ran_conn *conn);
-int msc_mgcp_call_complete(struct gsm_trans *trans, uint16_t port, char *addr);
-int msc_mgcp_call_release(struct gsm_trans *trans);
diff --git a/include/osmocom/msc/msc_roles.h b/include/osmocom/msc/msc_roles.h
new file mode 100644
index 000000000..a1fab2f23
--- /dev/null
+++ b/include/osmocom/msc/msc_roles.h
@@ -0,0 +1,387 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsup.h>
+
+#include <osmocom/msc/msc_common.h>
+#include <osmocom/msc/ran_infra.h>
+
+/* Each subscriber connection is managed by different roles, as described in 3GPP TS 49.008 '4.3 Roles of MSC-A, MSC-I
+ * and MSC-T':
+ *
+ * MSC-A: subscriber management and control of all transactions (CC, SMS, USSD,...)
+ * MSC-I: "internal": the actual BSSMAP link to the BSS, or RANAP link to the RNC.
+ * MSC-T: "transitory": a new pending RAN link to a BSS or RNC, while handover is in progress.
+ * MSC-T becomes the new MSC-I once handover ends successfully.
+ *
+ * Without inter-MSC handover involved, all of the roles are managed by a single MSC instance. During inter-MSC
+ * handover negotiation, an MSC-T is set up at a remote MSC while MSC-A remains in the original MSC, and when handover
+ * concludes successfully, the remote MSC-T becomes the new remote MSC-I, replacing the local MSC-I role.
+ *
+ * Furthermore, the 3GPP specs use the following terms for naming MSC locations: MSC-A, MSC-B and MSC-B', as well as BSS
+ * or BSS-A, BSS-B and BSS-B':
+ *
+ * MSC-A: the first MSC the subscriber connected to.
+ * MSC-B: a remote MSC (if any).
+ * MSC-B': another remote MSC (if any, during Subsequent Handover).
+ *
+ * The full role assignments are spelled out in 3GPP TS 29.002.
+ *
+ * In Osmocom, the MAP protocol spoken between the MSCs is modeled using GSUP instead.
+ *
+ * Here are some diagrams of the lifecycle of a single subscriber's MSC-A,-I,-T roles at the locations MSC-A, MSC-B and
+ * MSC-B'.
+ *
+ * Initially:
+ *
+ * [MSC-A]
+ * BSS <-> MSC-I
+ *
+ * Then during inter-MSC handover negotiation:
+ *
+ * [MSC-A] <-MAP-> MSC-B
+ * BSS <-> MSC-I MSC-T <-> new BSS
+ *
+ * and when successful:
+ *
+ * [MSC-A] <-MAP-> MSC-B
+ * MSC-I <-> BSS
+ *
+ * Additional subsequent handover:
+ *
+ * [MSC-A] <-MAP-> MSC-B
+ * ^ MSC-I <-> BSS
+ * |
+ * +-------MAP-> MSC-B'
+ * MSC-T <-> new BSS
+ *
+ * (Here, quote, MSC-A "shall act as the target BSS towards the MSC-I and as the MSC towards the MSC-T.")
+ * and when successful:
+ *
+ * [MSC-A]
+ * ^
+ * |
+ * +-------MAP-> MSC-B
+ * MSC-I <-> BSS
+ *
+ * Subsequent handover back to the original MSC:
+ *
+ * [MSC-A] <-MAP-> MSC-B
+ * new BSS <-> MSC-T MSC-I <-> BSS
+ *
+ * and then
+ * [MSC-A]
+ * BSS <-> MSC-I
+ *
+ *
+ * Inter-BSC Handover is just a special case of inter-MSC Handover, where the same MSC-A takes on both MSC-I and MSC-T
+ * roles:
+ *
+ * [MSC-A]
+ * BSS <-> MSC-I
+ * new BSS <-> MSC-T
+ *
+ * The mechanism to take on different roles is implemented by different FSM instances. Each FSM kind has one
+ * implementation that acts locally, and another implementation to forward to a remote MSC. For example, in this
+ * scenario:
+ *
+ * [MSC-A] <-MAP-> MSC-B
+ * MSC-I <-> BSS
+ *
+ * the implementation is
+ *
+ * [MSC-A-----------------] [MSC-B-----------------]
+ * msc_a <-> msc_i_REMOTE <---GSUP---> msc_a_REMOTE <-> msc_i <--BSSMAP--> [BSS]
+ *
+ * MSC-A has a locally acting msc_a FSM implementation. The msc_i FSM implementation at MSC-A receives signals from the
+ * msc_a FSM and "merely" sends the MAP instructions to MSC-B.
+ *
+ * At MSC-B, in turn, the msc_a FSM's "remote" implementation receives the MAP messages and dispatches according events
+ * to the MSC-B's local msc_i FSM instance, which is implemented to directly act towards the BSS.
+ *
+ * To implement single-MSC operation, we have the separate MSC roles' local implementations on the same MSC instance
+ * instead of forwarding.
+ *
+ *
+ * Use of MAP procedures on GSUP towards HLR:
+ *
+ * The MSC <-> VLR communication does still happen locally in the MSC-A only. In other words, there may be MAP message
+ * handling between the MSCs (in the form of GSUP), but no MAP to talk to our internal VLR.
+ *
+ * From the VLR to the HLR, though, we again use GSUP for subscriber related HLR operations such as LU requesting and
+ * retrieving auth tokens.
+ *
+ * To complete the picture, the MSC-A <--GSUP--> MSC-B forwarding happens over the same GSUP connection
+ * as the VLR <--GSUP--> HLR link:
+ *
+ * OsmoMSC
+ * MSC-A <----------E-interface--->+--GSUP--> [IPA routing] ----E--> MSC-B
+ * ^ ^ (in osmo-hlr) \
+ * | (internal API) / \--D--> HLR
+ * v /
+ * VLR <------------D-interface-/
+ */
+
+struct inter_msc_link;
+struct ran_conn;
+
+enum msc_role {
+ MSC_ROLE_A,
+ MSC_ROLE_I,
+ MSC_ROLE_T,
+
+ MSC_ROLES_COUNT
+};
+
+extern const struct value_string msc_role_names[];
+static inline const char *msc_role_name(enum msc_role role)
+{ return get_value_string(msc_role_names, role); }
+
+
+enum msc_common_events {
+ /* Explicitly start with 0 (first real event will be -1 + 1 = 0). */
+ OFFSET_MSC_COMMON_EV = -1,
+
+ MSC_REMOTE_EV_RX_GSUP,
+
+ MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
+ MSC_EV_CALL_LEG_RTP_COMPLETE,
+ MSC_EV_CALL_LEG_RTP_RELEASED,
+ MSC_EV_CALL_LEG_TERM,
+
+ /* MNCC has told us to RTP_CREATE, but local RTP port has not yet been set up.
+ * The MSC role should respond by calling mncc_set_rtp_stream() */
+ MSC_MNCC_EV_NEED_LOCAL_RTP,
+ MSC_MNCC_EV_CALL_PROCEEDING,
+ MSC_MNCC_EV_CALL_COMPLETE,
+ MSC_MNCC_EV_CALL_ENDED,
+
+ LAST_MSC_COMMON_EV,
+};
+
+
+/* The events that the msc_a_local and msc_a_remote FSM implementations can receive,
+ * according to specifications. Not all of these are necessarily implemented. */
+enum msc_a_events {
+ OFFSET_MSC_A_EV = LAST_MSC_COMMON_EV - 1,
+
+ /* Establishing Layer 3 happens only at MSC-A (all-local MSC). To distinguish from the inter-MSC DTAP
+ * forwarding, keep this as a separate event. */
+ MSC_A_EV_FROM_I_COMPLETE_LAYER_3,
+
+ /* In inter-MSC situations, DTAP is forwarded transparently in AN-APDU IEs (formerly named
+ * BSS-APDU); see
+ * - 3GPP TS 49.008 4.2 'Transfer of DTAP and BSSMAP layer 3 messages on the * E-interface',
+ * - 3GPP TS 29.010 4.5.4 'BSSAP Messages transfer on E-Interface',
+ * - 3GPP TS 29.002 8.4.3 MAP_PROCESS_ACCESS_SIGNALLING service, 8.4.4 MAP_FORWARD_ACCESS_SIGNALLING service.
+ *
+ * MSC-B ---DTAP--> MSC-A MAP PROCESS ACCESS SIGNALLING request
+ * MSC-B <--DTAP--- MSC-A MAP FORWARD ACCESS SIGNALLING request
+ * (where neither will receive a "response")
+ *
+ * See 3GPP TS 49.008 6. 'BSSMAP messages transferred on the E-interface'.
+ * Depending on the RAN, the AN-APDU contains a BSSMAP or a RANAP encoded message.
+ * MSC-I to MSC-A:
+ * - Managing attach to one BSC+MSC:
+ * - CLASSMARK_UPDATE,
+ * - CIPHER_MODE_COMPLETE,
+ * - CIPHER_MODE_REJECT,
+ * - ASSIGNMENT_COMPLETE,
+ * - ASSIGNMENT_FAILURE,
+ * - CLEAR_REQUEST,
+ * - Handover related messages:
+ * - HANDOVER_REQUEST,
+ * - HANDOVER_PERFORMED,
+ * - HANDOVER_FAILURE,
+ * - Messages we don't need/support yet:
+ * - CHANNEL_MODIFY_REQUEST (MSC assisted codec changing handover),
+ * - SAPI_N_REJECT,
+ * - CONFUSION,
+ * - BSS_INVOKE_TRACE,
+ * - QUEUING_INDICATION,
+ * - PERFORM_LOCATION_REQUEST (*not* related to a Location Updating, but about passing the MS's geological
+ * position)
+ * - PERFORM_LOCATION_ABORT,
+ * - PERFORM_LOCATION_RESPONSE,
+ * - CONNECTION_ORIENTED_INFORMATION is listed in 48.008 3.2.1.70 as "(void)",
+ */
+ MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST,
+ MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST,
+
+ /* See 3GPP TS 29.002 8.4.2 MAP_SEND_END_SIGNAL service. */
+ MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST,
+
+ /* These BSSMAP messages are relevant for MSC-T -> MSC-A, i.e. from the transitory during inter-MSC handover:
+ *
+ * - Handover related messages:
+ * - HANDOVER_REQUEST_ACKNOWLEDGE,
+ * - HANDOVER_COMPLETE,
+ * - HANDOVER_FAILURE,
+ * - HANDOVER_DETECT,
+ * - CLEAR_REQUEST,
+ * - Messages we don't need/support yet:
+ * - CONFUSION,
+ * - QUEUING_INDICATION,
+ */
+ MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
+
+ /* Essentially the HO Request Ack. 3GPP TS 29.002 8.4.1 MAP_PREPARE_HANDOVER service. */
+ MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE,
+ MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE,
+
+ /* Done establishing the radio link to the MS, for Handover.
+ * See 3GPP TS 29.002 8.4.2 MAP_SEND_END_SIGNAL service.
+ * Not to be confused with the MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE that tells MSC-B to release. */
+ MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST,
+
+ /* gsm_04_08.c has successfully received a valid Complete Layer 3 message, i.e. Location Updating, CM Service
+ * Request, Paging Reponse or IMSI Detach. */
+ MSC_A_EV_COMPLETE_LAYER_3_OK,
+
+ /* Received a Classmark Update -- during GERAN ciphering, msc_a may have to wait for Classmark information to
+ * determine supported ciphers. */
+ MSC_A_EV_CLASSMARK_UPDATE,
+
+ /* LU or Process Access FSM have determined that the peer has verified its authenticity. */
+ MSC_A_EV_AUTHENTICATED,
+
+ /* A valid request is starting to be processed on the connection. Upon this event, msc_a moves from
+ * MSC_A_ST_AUTHENTICATED to MSC_A_ST_COMMUNICATING, and enters the only state without an expiry timeout. */
+ MSC_A_EV_TRANSACTION_ACCEPTED,
+
+ /* MSC originated close request, e.g. all done, failed authentication, ... */
+ MSC_A_EV_CN_CLOSE,
+
+ /* Subscriber originated close request */
+ MSC_A_EV_MO_CLOSE,
+
+ /* msc_a->use_count has reached a total of zero. */
+ MSC_A_EV_UNUSED,
+
+ MSC_A_EV_HANDOVER_REQUIRED,
+ MSC_A_EV_HANDOVER_END,
+
+ /* indicates nr of MSC_A events, keep this as last enum value */
+ LAST_MSC_A_EV
+};
+osmo_static_assert(LAST_MSC_A_EV <= 32, not_too_many_msc_a_events);
+
+extern const struct value_string msc_a_fsm_event_names[];
+
+enum msc_from_ran_events {
+ OFFSET_MSC_EV_FROM_RAN = LAST_MSC_COMMON_EV - 1,
+
+ MSC_EV_FROM_RAN_COMPLETE_LAYER_3,
+
+ /* A BSSMAP/RANAP message came in on the RAN conn. */
+ MSC_EV_FROM_RAN_UP_L2,
+
+ /* The RAN connection is gone, or busy going. */
+ MSC_EV_FROM_RAN_CONN_RELEASED,
+
+ LAST_MSC_EV_FROM_RAN
+};
+
+/* The events that the msc_i_local and msc_i_remote FSM implementations can receive.
+ * The MSC-I can also receive all msc_common_events and msc_from_ran_events. */
+enum msc_i_events {
+ OFFSET_E_MSC_I = LAST_MSC_EV_FROM_RAN - 1,
+
+ /* BSSMAP/RANAP comes in from MSC-A to be sent out on the RAN conn.
+ * Depending on the RAN, the AN-APDU contains a BSSMAP or a RANAP encoded message.
+ * Relevant BSSMAP procedures, see 3GPP TS 49.008 6. 'BSSMAP messages transferred on the E-interface':
+ * - Managing attach to one BSC+MSC:
+ * - CLASSMARK_REQUEST,
+ * - CIPHER_MODE_COMMAND,
+ * - COMMON_ID,
+ * - ASSIGNMENT_REQUEST,
+ * - Handover related messages:
+ * - HANDOVER_REQUEST_ACKNOWLEDGE,
+ * - HANDOVER_FAILURE,
+ * - Messages we don't need/support yet:
+ * - CONFUSION,
+ * - MSC_INVOKE_TRACE,
+ * - QUEUING_INDICATION,
+ * - LSA_INFORMATION,
+ * - PERFORM_LOCATION_REQUEST, (*not* related to a Location Updating, but about passing the MS's geological position)
+ * - PERFORM_LOCATION_ABORT,
+ * - PERFORM_LOCATION_RESPONSE,
+ * - CONNECTION_ORIENTED_INFORMATION is listed in 48.008 3.2.1.70 as "(void)"
+ */
+ MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST,
+
+ /* MSC-A tells us to release the RAN connection. */
+ MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE,
+
+ MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT,
+ MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR,
+
+ LAST_MSC_I_EV
+};
+osmo_static_assert(LAST_MSC_I_EV <= 32, not_too_many_msc_i_events);
+
+extern const struct value_string msc_i_fsm_event_names[];
+
+/* The events that the msc_t_local and msc_t_remote FSM implementations can receive.
+ * The MSC-T can also receive all msc_common_events and msc_from_ran_events. */
+enum msc_t_events {
+ /* sufficient would be to use LAST_MSC_EV_FROM_RAN as offset. But while we have enough numbers
+ * available, it is a good idea to keep MSC-I and MSC-T events separate, to catch errors of
+ * sending wrong event kinds. */
+ OFFSET_MSC_T_EV = LAST_MSC_I_EV - 1,
+
+ /* BSSMAP/RANAP comes in from MSC-A to be sent out on the RAN conn.
+ * Relevant BSSMAP procedures, see 3GPP TS 49.008 6. 'BSSMAP messages transferred on the E-interface':
+ * - Handover related messages:
+ * - HANDOVER_REQUEST,
+ * - CLASSMARK_UPDATE, (?)
+ * - Messages we don't need/support yet:
+ * - CONFUSION,
+ * - MSC_INVOKE_TRACE,
+ * - BSS_INVOKE_TRACE,
+ */
+ MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST,
+ MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST,
+
+ /* MSC originated close request, e.g. all done, failed handover, ... */
+ MSC_T_EV_CN_CLOSE,
+
+ /* Subscriber originated close request */
+ MSC_T_EV_MO_CLOSE,
+
+ MSC_T_EV_CLEAR_COMPLETE,
+
+ LAST_MSC_T_EV
+};
+osmo_static_assert(LAST_MSC_T_EV <= 32, not_too_many_msc_t_events);
+
+extern const struct value_string msc_t_fsm_event_names[];
+
+/* All MSC role FSM implementations share this at the start of their fi->priv struct.
+ * See struct msc_a, struct msc_i, struct msc_t in their individual headers. */
+struct msc_role_common {
+ enum msc_role role;
+
+ struct osmo_fsm_inst *fi;
+
+ /* For a local implementation, this is NULL. Otherwise, this identifies how to reach the remote
+ * MSC that this "remote" implementation forwards messages to. */
+ struct e_link *remote_to;
+
+ struct msub *msub;
+ struct gsm_network *net;
+ struct ran_infra *ran;
+};
+
+/* AccessNetworkSignalInfo as in 3GPP TS 29.002. */
+struct an_apdu {
+ /* accessNetworkProtocolId */
+ enum osmo_gsup_access_network_protocol an_proto;
+ /* signalInfo */
+ struct msgb *msg;
+ /* If this AN-APDU is sent between MSCs, additional information from the E-interface messaging, like the
+ * Handover Number, will placed/available here. Otherwise may be left NULL. */
+ const struct osmo_gsup_message *e_info;
+};
diff --git a/include/osmocom/msc/msc_t.h b/include/osmocom/msc/msc_t.h
new file mode 100644
index 000000000..39b3abca0
--- /dev/null
+++ b/include/osmocom/msc/msc_t.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <osmocom/msc/msc_roles.h>
+
+struct ran_conn;
+struct ran_infra;
+struct ran_peer;
+struct gsm_mncc;
+struct mncc_call;
+
+#define LOG_MSC_T(MSC_T, LEVEL, FMT, ARGS ...) \
+ LOG_MSC_T_CAT(MSC_T, (MSC_T) ? (MSC_T)->c.ran->log_subsys : DMSC, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_T_CAT(MSC_T, SUBSYS, LEVEL, FMT, ARGS ...) \
+ LOGPFSMSL((MSC_T) ? (MSC_T)->c.fi : NULL, SUBSYS, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_T_CAT_SRC(MSC_T, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ARGS ...) \
+ LOGPFSMSLSRC((MSC_T) ? (MSC_T)->c.fi : NULL, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ## ARGS)
+
+struct msc_t {
+ /* struct msc_role_common must remain at start */
+ struct msc_role_common c;
+
+ struct ran_conn *ran_conn;
+
+ struct {
+ uint8_t chosen_channel;
+ uint8_t chosen_encr_alg;
+ uint8_t chosen_speech_version;
+ } geran;
+
+ struct {
+ struct an_apdu ho_request;
+ struct gsm0808_cell_id cell_id_target;
+ uint32_t callref;
+ char handover_number[16]; /* No libosmocore definition for MSISDN_MAXLEN? */
+ struct call_leg *call_leg;
+ struct mncc_call *mncc_forwarding_to_remote_cn;
+ } inter_msc;
+
+ struct osmo_gsm48_classmark classmark;
+ bool ho_success;
+ bool ho_fail_sent;
+};
+
+enum msc_t_state {
+ MSC_T_ST_PENDING_FIRST_CO_INITIAL_MSG,
+ MSC_T_ST_WAIT_LOCAL_RTP,
+ MSC_T_ST_WAIT_HO_REQUEST_ACK,
+ MSC_T_ST_WAIT_HO_COMPLETE,
+};
+
+struct msc_t *msc_t_alloc_without_ran_peer(struct msub *msub, struct ran_infra *ran);
+int msc_t_set_ran_peer(struct msc_t *msc_t, struct ran_peer *ran_peer);
+struct msc_t *msc_t_alloc(struct msub *msub, struct ran_peer *ran_peer);
+int msc_t_down_l2_co(struct msc_t *msc_t, const struct an_apdu *an_apdu, bool initial);
+void msc_t_clear(struct msc_t *msc_t);
+
+struct gsm_network *msc_t_net(const struct msc_t *msc_t);
+struct vlr_subscr *msc_t_vsub(const struct msc_t *msc_t);
+
+struct mncc_call *msc_t_check_call_to_handover_number(const struct gsm_mncc *msg);
diff --git a/include/osmocom/msc/msc_t_remote.h b/include/osmocom/msc/msc_t_remote.h
new file mode 100644
index 000000000..170505405
--- /dev/null
+++ b/include/osmocom/msc/msc_t_remote.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#define LOG_MSC_T_REMOTE(MSC_T_REMOTE, LEVEL, FMT, ARGS ...) \
+ LOG_MSC_T_REMOTE_CAT(MSC_T_REMOTE, (MSC_T_REMOTE) ? (MSC_T_REMOTE)->c.ran->log_subsys : DMSC, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_T_REMOTE_CAT(MSC_T_REMOTE, SUBSYS, LEVEL, FMT, ARGS ...) \
+ LOGPFSMSL((MSC_T_REMOTE) ? (MSC_T_REMOTE)->c.fi : NULL, SUBSYS, LEVEL, FMT, ## ARGS)
+#define LOG_MSC_T_REMOTE_CAT_SRC(MSC_T_REMOTE, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ARGS ...) \
+ LOGPFSMSLSRC((MSC_T_REMOTE) ? (MSC_T_REMOTE)->c.fi : NULL, SUBSYS, LEVEL, SRCFILE, LINE, FMT, ## ARGS)
+
+struct msub;
+struct ran_infra;
+
+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);
diff --git a/include/osmocom/msc/msub.h b/include/osmocom/msc/msub.h
new file mode 100644
index 000000000..2418febcf
--- /dev/null
+++ b/include/osmocom/msc/msub.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/msc_roles.h>
+
+struct vlr_subscr;
+struct gsm_network;
+enum gsm48_gsm_cause;
+enum complete_layer3_type;
+enum osmo_gsup_access_network_protocol;
+
+#define VSUB_USE_MSUB "active-conn"
+
+struct msub {
+ struct llist_head entry;
+ struct osmo_fsm_inst *fi;
+
+ struct vlr_subscr *vsub;
+
+ /* role = {MSC_ROLE_A, MSC_ROLE_I, MSC_ROLE_T} */
+ struct osmo_fsm_inst *role[MSC_ROLES_COUNT];
+ struct gsm_network *net;
+};
+
+extern struct llist_head msub_list;
+
+#define LOG_MSUB_CAT_SRC(msub, cat, level, file, line, fmt, args ...) \
+ LOGPSRC(cat, level, file, line, "(%s) " fmt, msub_name(msub), ## args)
+
+#define LOG_MSUB_CAT(msub, cat, level, fmt, args ...) \
+ LOGP(cat, level, "msub(%s) " fmt, msub_name(msub), ## args)
+
+#define LOG_MSUB(msub, level, fmt, args ...) \
+ LOG_MSUB_CAT(msub, DMSC, level, fmt, ## args)
+
+struct msub *msub_alloc(struct gsm_network *net);
+
+#define msub_role_alloc(MSUB, ROLE, FSM, ROLE_STRUCT, RAN) \
+ (ROLE_STRUCT*)_msub_role_alloc(MSUB, ROLE, FSM, sizeof(ROLE_STRUCT), #ROLE_STRUCT ":" #FSM, RAN)
+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);
+
+const char *msub_name(const struct msub *msub);
+
+struct msub *msub_for_vsub(const struct vlr_subscr *for_vsub);
+
+void msub_set_role(struct msub *msub, struct osmo_fsm_inst *msc_role);
+void msub_remove_role(struct msub *msub, struct osmo_fsm_inst *fi);
+
+struct msc_a *msub_msc_a(const struct msub *msub);
+struct msc_i *msub_msc_i(const struct msub *msub);
+struct msc_t *msub_msc_t(const struct msub *msub);
+struct ran_conn *msub_ran_conn(const struct msub *msub);
+const char *msub_ran_conn_name(const struct msub *msub);
+
+int msub_set_vsub(struct msub *msub, struct vlr_subscr *vsub);
+struct vlr_subscr *msub_vsub(const struct msub *msub);
+struct gsm_network *msub_net(const struct msub *msub);
+
+int msub_role_to_role_event(struct msub *msub, enum msc_role from_role, enum msc_role to_role);
+#define msub_role_dispatch(MSUB, TO_ROLE, TO_ROLE_EVENT, AN_APDU) \
+ _msub_role_dispatch(MSUB, TO_ROLE, TO_ROLE_EVENT, AN_APDU, __FILE__, __LINE__)
+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);
+int msub_tx_an_apdu(struct msub *msub, enum msc_role from_role, enum msc_role to_role, struct an_apdu *an_apdu);
+
+void msub_update_id_from_mi(struct msub *msub, const uint8_t mi[], uint8_t mi_len);
+void msub_update_id(struct msub *msub);
+void msub_update_id_for_vsub(struct vlr_subscr *for_vsub);
+
+void msub_pending_cm_service_req_add(struct msub *msub, enum osmo_cm_service_type type);
+unsigned int msub_pending_cm_service_req_count(struct msub *msub, enum osmo_cm_service_type type);
+void msub_pending_cm_service_req_del(struct msub *msub, enum osmo_cm_service_type type);
+
+void msc_role_forget_conn(struct osmo_fsm_inst *role, struct ran_conn *conn);
+
+struct msgb *msc_role_ran_encode(struct osmo_fsm_inst *role, const struct ran_msg *ran_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);
diff --git a/include/osmocom/msc/neighbor_ident.h b/include/osmocom/msc/neighbor_ident.h
new file mode 100644
index 000000000..8cd74ab57
--- /dev/null
+++ b/include/osmocom/msc/neighbor_ident.h
@@ -0,0 +1,68 @@
+/* Manage identity of neighboring BSS cells for inter-BSC handover */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/sccp_sap.h>
+
+struct vty;
+struct gsm_network;
+
+enum msc_neighbor_type {
+ MSC_NEIGHBOR_TYPE_NONE = 0,
+ MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER,
+ MSC_NEIGHBOR_TYPE_REMOTE_MSC,
+};
+
+struct msc_ipa_name {
+ char buf[64];
+ size_t len;
+};
+
+int msc_ipa_name_from_str(struct msc_ipa_name *min, const char *name);
+int msc_ipa_name_cmp(const struct msc_ipa_name *a, const struct msc_ipa_name *b);
+
+struct neighbor_ident_addr {
+ enum osmo_rat_type ran_type;
+ enum msc_neighbor_type type;
+ union {
+ char local_ran_peer_pc_str[23];
+ struct msc_ipa_name remote_msc_ipa_name;
+ };
+};
+
+struct neighbor_ident_entry {
+ struct llist_head entry;
+
+ struct neighbor_ident_addr addr;
+
+ /* A list of struct cell_ids_entry. A gsm0808_cell_id_list2 would in principle suffice, but to support
+ * storing more than 127 cell ids and to allow storing IDs of differing types, have a list of any number of
+ * gsm0808_cell_id_list2. */
+ struct llist_head cell_ids;
+};
+
+void neighbor_ident_init(struct gsm_network *net);
+const char *neighbor_ident_addr_name(const struct neighbor_ident_addr *nia);
+
+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);
+
+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);
+
+const struct neighbor_ident_entry *neighbor_ident_find_by_addr(const struct llist_head *ni_list,
+ const struct neighbor_ident_addr *nia);
+
+void neighbor_ident_del(const struct neighbor_ident_entry *nie);
+
+void neighbor_ident_clear(struct llist_head *ni_list);
+
+void neighbor_ident_vty_init(struct gsm_network *net);
+void neighbor_ident_vty_write(struct vty *vty);
+
diff --git a/include/osmocom/msc/paging.h b/include/osmocom/msc/paging.h
new file mode 100644
index 000000000..4de679df7
--- /dev/null
+++ b/include/osmocom/msc/paging.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+
+struct msc_a;
+struct vlr_subscr;
+struct gsm_trans;
+
+/* Modeled after the RANAP PagingCause; translates to enum sgsap_service_ind and BSSMAP Channel Needed (3GPP TS 48.008
+ * 3.2.2.36) by collapsing e.g. all call related paging causes to SGSAP_SERV_IND_CS_CALL, etc. */
+enum paging_cause {
+ PAGING_CAUSE_CALL_CONVERSATIONAL = 0,
+ PAGING_CAUSE_CALL_STREAMING,
+ PAGING_CAUSE_CALL_INTERACTIVE,
+ PAGING_CAUSE_CALL_BACKGROUND,
+ PAGING_CAUSE_SIGNALLING_LOW_PRIO,
+ PAGING_CAUSE_SIGNALLING_HIGH_PRIO,
+ PAGING_CAUSE_UNSPECIFIED,
+};
+
+extern const struct value_string paging_cause_names[];
+static inline const char *paging_cause_name(enum paging_cause val)
+{ return get_value_string(paging_cause_names, val); }
+
+/* A successful Paging will pass a valid msc_a, an expired paging will pass msc_a == NULL. */
+typedef void (* paging_cb_t )(struct msc_a *msc_a, struct gsm_trans *trans);
+
+struct paging_request {
+ struct llist_head entry;
+
+ /* human readable label to be able to log pending request kinds */
+ const char *label;
+ enum paging_cause cause;
+
+ /* the callback data */
+ paging_cb_t paging_cb;
+ struct gsm_trans *trans;
+};
+
+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);
+void paging_request_remove(struct paging_request *pr);
+
+void paging_response(struct msc_a *msc_a);
+void paging_expired(struct vlr_subscr *vsub);
diff --git a/include/osmocom/msc/ran_conn.h b/include/osmocom/msc/ran_conn.h
index 0b99e252c..7aa50df07 100644
--- a/include/osmocom/msc/ran_conn.h
+++ b/include/osmocom/msc/ran_conn.h
@@ -3,238 +3,31 @@
#include <stdint.h>
-#include <osmocom/gsm/protocol/gsm_04_08.h>
-#include <osmocom/sigtran/sccp_sap.h>
-#include <osmocom/mgcp_client/mgcp_client.h>
-#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/linuxlist.h>
-#define LOG_RAN_CONN(conn, level, fmt, args ...) \
- LOG_RAN_CONN_CAT(conn, (conn) ? (conn)->log_subsys : DMSC, level, fmt, ## args)
-
-#define LOG_RAN_CONN_CAT(conn, subsys, level, fmt, args ...) \
- LOGPFSMSL((conn)? (conn)->fi : NULL, subsys, level, fmt, ## args)
-
-#define VSUB_USE_CONN "conn"
-
-enum ran_conn_fsm_event {
- /* Accepted the initial Complete Layer 3 (starting to evaluate Authentication and Ciphering) */
- RAN_CONN_E_COMPLETE_LAYER_3,
- /* Received Classmark Update, typically neede for Ciphering Mode Command */
- RAN_CONN_E_CLASSMARK_UPDATE,
- /* LU or Process Access FSM has determined that this conn is good */
- RAN_CONN_E_ACCEPTED,
- /* received first reply from MS in "real" CC, SMS, USSD communication */
- RAN_CONN_E_COMMUNICATING,
- /* Some async action has completed, check again whether all is done */
- RAN_CONN_E_RELEASE_WHEN_UNUSED,
- /* MS/BTS/BSC originated close request */
- RAN_CONN_E_MO_CLOSE,
- /* MSC originated close request, e.g. failed authentication */
- RAN_CONN_E_CN_CLOSE,
- /* The usage count for the conn has reached zero */
- RAN_CONN_E_UNUSED,
-};
-
-enum ran_conn_fsm_state {
- RAN_CONN_S_NEW,
- RAN_CONN_S_AUTH_CIPH,
- RAN_CONN_S_WAIT_CLASSMARK_UPDATE,
- RAN_CONN_S_ACCEPTED,
- RAN_CONN_S_COMMUNICATING,
- RAN_CONN_S_RELEASING,
- RAN_CONN_S_RELEASED,
-};
-
-enum integrity_protection_state {
- INTEGRITY_PROTECTION_NONE = 0,
- INTEGRITY_PROTECTION_IK = 1,
- INTEGRITY_PROTECTION_IK_CK = 2,
-};
-
-enum complete_layer3_type {
- COMPLETE_LAYER3_NONE,
- COMPLETE_LAYER3_LU,
- COMPLETE_LAYER3_CM_SERVICE_REQ,
- COMPLETE_LAYER3_PAGING_RESP,
-};
-
-#define MAX_A5_KEY_LEN (128/8)
-
-struct geran_encr {
- uint8_t alg_id;
- uint8_t key_len;
- uint8_t key[MAX_A5_KEY_LEN];
-};
-
-extern const struct value_string complete_layer3_type_names[];
-static inline const char *complete_layer3_type_name(enum complete_layer3_type val)
-{
- return get_value_string(complete_layer3_type_names, val);
-}
-
-struct gsm_classmark {
- bool classmark1_set;
- struct gsm48_classmark1 classmark1;
- uint8_t classmark2_len;
- uint8_t classmark2[3];
- uint8_t classmark3_len;
- uint8_t classmark3[14]; /* if cm3 gets extended by spec, it will be truncated */
-};
+struct ran_peer;
+struct osmo_fsm_inst;
+struct msgb;
/* active radio connection of a mobile subscriber */
struct ran_conn {
- /* global linked list of ran_conn instances */
+ /* Entry in sccp_ran_inst->ran_conns */
struct llist_head entry;
- /* FSM instance to control the RAN connection's permissions and lifetime. */
- struct osmo_fsm_inst *fi;
- enum complete_layer3_type complete_layer3_type;
-
- /* usage count. If this drops to zero, we start the release
- * towards A/Iu */
- uint32_t use_count;
- uint32_t use_tokens;
-
- /* The MS has opened the conn with a CM Service Request, and we shall
- * keep it open for an actual request (or until timeout). */
- bool received_cm_service_request;
-
- /* libmsc/libvlr subscriber information (if available) */
- struct vlr_subscr *vsub;
-
- /* LU expiration handling */
- uint8_t expire_timer_stopped;
-
- /* Are we part of a special "silent" call */
- int silent_call;
+ struct ran_peer *ran_peer;
+ uint32_t sccp_conn_id;
- /* back pointers */
- struct gsm_network *network;
+ /* MSC role that this RAN connection belongs to. This will be either an msc_i (currently active
+ * connection) or an msc_t (transitory new connection during Handover). */
+ struct osmo_fsm_inst *msc_role;
- /* connected via 2G or 3G? */
- enum osmo_rat_type via_ran;
- /* whether to log on DBSSAP, DIUCS, ... */
- int log_subsys;
-
- uint16_t lac;
- struct geran_encr geran_encr;
-
- /* "Temporary" storage for the case the VLR asked for Cipher Mode Command, but the MSC still
- * wants to request a Classmark Update first. */
- struct {
- bool umts_aka;
- bool retrieve_imeisv;
- } geran_set_cipher_mode;
-
- /* N(SD) expected in the received frame, per flow (TS 24.007 11.2.3.2.3.2.2) */
- uint8_t n_sd_next[4];
-
- struct {
- struct mgcp_ctx *mgcp_ctx;
- unsigned int mgcp_rtp_endpoint;
-
- uint16_t local_port_ran;
- char local_addr_ran[INET_ADDRSTRLEN];
- uint16_t remote_port_ran;
- char remote_addr_ran[INET_ADDRSTRLEN];
- enum mgcp_codecs codec_ran;
-
- uint16_t local_port_cn;
- char local_addr_cn[INET_ADDRSTRLEN];
- uint16_t remote_port_cn;
- char remote_addr_cn[INET_ADDRSTRLEN];
- enum mgcp_codecs codec_cn;
- } rtp;
-
- /* which Iu-CS connection, if any. */
- struct {
- struct ranap_ue_conn_ctx *ue_ctx;
- uint8_t rab_id;
- bool waiting_for_release_complete;
- } iu;
-
- struct {
- /* A pointer to the SCCP user that handles
- * the SCCP connections for this subscriber
- * connection */
- struct osmo_sccp_user *scu;
-
- /* The address of the BSC that is associated
- * with this RAN connection */
- struct osmo_sccp_addr bsc_addr;
-
- /* The connection identifier that is used
- * to reference the SCCP connection that is
- * associated with this RAN connection */
- uint32_t conn_id;
-
- bool waiting_for_clear_complete;
- } a;
-
- /* Temporary storage for Classmark Information for times when a connection has no VLR subscriber
- * associated yet. It will get copied to the VLR subscriber upon msc_vlr_subscr_assoc(). */
- struct gsm_classmark temporary_classmark;
-};
-
-struct ran_conn *ran_conn_alloc(struct gsm_network *network, enum osmo_rat_type via_ran, uint16_t lac);
-
-void ran_conn_update_id_from_mi(struct ran_conn *conn, const uint8_t *mi, uint8_t mi_len);
-void ran_conn_update_id(struct ran_conn *conn);
-const char *ran_conn_get_conn_id(struct ran_conn *conn);
-void ran_conn_update_id_for_vsub(struct vlr_subscr *for_vsub);
-
-void ran_conn_complete_layer_3(struct ran_conn *conn);
-
-void ran_conn_sapi_n_reject(struct ran_conn *conn, int dlci);
-int ran_conn_clear_request(struct ran_conn *conn, uint32_t cause);
-void ran_conn_compl_l3(struct ran_conn *conn,
- struct msgb *msg, uint16_t chosen_channel);
-void ran_conn_dtap(struct ran_conn *conn, struct msgb *msg);
-int ran_conn_classmark_request_then_cipher_mode_cmd(struct ran_conn *conn, bool umts_aka,
- bool retrieve_imeisv);
-int ran_conn_geran_set_cipher_mode(struct ran_conn *conn, bool umts_aka, bool retrieve_imeisv);
-void ran_conn_cipher_mode_compl(struct ran_conn *conn, struct msgb *msg, uint8_t alg_id);
-void ran_conn_rx_sec_mode_compl(struct ran_conn *conn);
-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);
-void ran_conn_assign_fail(struct ran_conn *conn, uint8_t cause, uint8_t *rr_cause);
-
-void ran_conn_init(void);
-bool ran_conn_is_accepted(const struct ran_conn *conn);
-bool ran_conn_is_establishing_auth_ciph(const struct ran_conn *conn);
-void ran_conn_communicating(struct ran_conn *conn);
-void ran_conn_close(struct ran_conn *conn, uint32_t cause);
-void ran_conn_mo_close(struct ran_conn *conn, uint32_t cause);
-bool ran_conn_in_release(struct ran_conn *conn);
-
-void ran_conn_rx_bssmap_clear_complete(struct ran_conn *conn);
-void ran_conn_rx_iu_release_complete(struct ran_conn *conn);
-void ran_conn_sgs_release_sent(struct ran_conn *conn);
-
-enum ran_conn_use {
- RAN_CONN_USE_UNTRACKED = -1,
- RAN_CONN_USE_COMPL_L3,
- RAN_CONN_USE_DTAP,
- RAN_CONN_USE_AUTH_CIPH,
- RAN_CONN_USE_CM_SERVICE,
- RAN_CONN_USE_TRANS_CC,
- RAN_CONN_USE_TRANS_SMS,
- RAN_CONN_USE_TRANS_NC_SS,
- RAN_CONN_USE_SILENT_CALL,
- RAN_CONN_USE_RELEASE,
+ bool closing;
};
-extern const struct value_string ran_conn_use_names[];
-static inline const char *ran_conn_use_name(enum ran_conn_use val)
-{ return get_value_string(ran_conn_use_names, val); }
-
-#define ran_conn_get(conn, balance_token) \
- _ran_conn_get(conn, balance_token, __FILE__, __LINE__)
-#define ran_conn_put(conn, balance_token) \
- _ran_conn_put(conn, balance_token, __FILE__, __LINE__)
-struct ran_conn * _ran_conn_get(struct ran_conn *conn, enum ran_conn_use balance_token,
- const char *file, int line);
-void _ran_conn_put(struct ran_conn *conn, enum ran_conn_use balance_token,
- const char *file, int line);
-bool ran_conn_used_by(struct ran_conn *conn, enum ran_conn_use token);
+struct ran_conn *ran_conn_create_incoming(struct ran_peer *ran_peer, uint32_t sccp_conn_id);
+struct ran_conn *ran_conn_create_outgoing(struct ran_peer *ran_peer);
+const char *ran_conn_name(struct ran_conn *conn);
+int ran_conn_down_l2_co(struct ran_conn *conn, struct msgb *l3, bool initial);
+void ran_conn_msc_role_gone(struct ran_conn *conn, struct osmo_fsm_inst *msc_role);
+void ran_conn_close(struct ran_conn *conn);
+void ran_conn_discard(struct ran_conn *conn);
diff --git a/include/osmocom/msc/ran_infra.h b/include/osmocom/msc/ran_infra.h
new file mode 100644
index 000000000..38c424f09
--- /dev/null
+++ b/include/osmocom/msc/ran_infra.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/ran_msg.h>
+
+struct osmo_tdef;
+
+extern struct osmo_tdef msc_tdefs_geran[];
+extern struct osmo_tdef msc_tdefs_utran[];
+extern struct osmo_tdef msc_tdefs_sgs[];
+
+extern const struct value_string an_proto_names[];
+static inline const char *an_proto_name(enum osmo_gsup_access_network_protocol val)
+{ return get_value_string(an_proto_names, val); }
+
+struct ran_infra {
+ const enum osmo_rat_type type;
+ const enum osmo_gsup_access_network_protocol an_proto;
+ uint32_t ssn;
+ const int log_subsys;
+ struct osmo_tdef * const tdefs;
+ const struct sccp_ran_ops sccp_ran_ops;
+ const ran_dec_l2_t ran_dec_l2;
+ const ran_encode_t ran_encode;
+ struct sccp_ran_inst *sri;
+};
+
+extern struct ran_infra msc_ran_infra[];
+extern const int msc_ran_infra_len;
diff --git a/include/osmocom/msc/ran_msg.h b/include/osmocom/msc/ran_msg.h
new file mode 100644
index 000000000..4d0485d43
--- /dev/null
+++ b/include/osmocom/msc/ran_msg.h
@@ -0,0 +1,281 @@
+/* API to forward upcoming NAS events, e.g. from BSSAP and RANAP, to be handled by MSC-A or MSC-I. */
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/>.
+ */
+#pragma once
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+#include <osmocom/msc/msc_common.h>
+
+struct msgb;
+struct osmo_fsm_inst;
+
+#define LOG_RAN_DEC(NAS_DEC, subsys, level, fmt, args...) \
+ LOGPFSMSL((NAS_DEC)? (NAS_DEC)->caller_fi : NULL, subsys, level, "RAN decode: " fmt, ## args)
+
+#define LOG_RAN_ENC(FI, subsys, level, fmt, args...) \
+ LOGPFSMSL(FI, subsys, level, "RAN encode: " fmt, ## args)
+
+/* These message types are named after the BSSAP procedures in nas_a.h; most are also used for RANAP procedures of
+ * similar meaning in nas_iu.h. */
+enum ran_msg_type {
+ RAN_MSG_NONE = 0,
+ RAN_MSG_COMPL_L3,
+ RAN_MSG_DTAP,
+ RAN_MSG_CLEAR_COMMAND,
+ RAN_MSG_CLEAR_REQUEST,
+ RAN_MSG_CLEAR_COMPLETE,
+ RAN_MSG_CLASSMARK_REQUEST,
+ RAN_MSG_CLASSMARK_UPDATE,
+ RAN_MSG_CIPHER_MODE_COMMAND,
+ RAN_MSG_CIPHER_MODE_COMPLETE,
+ RAN_MSG_CIPHER_MODE_REJECT,
+ RAN_MSG_COMMON_ID,
+ RAN_MSG_ASSIGNMENT_COMMAND,
+ RAN_MSG_ASSIGNMENT_COMPLETE,
+ RAN_MSG_ASSIGNMENT_FAILURE,
+ RAN_MSG_SAPI_N_REJECT,
+ RAN_MSG_LCLS_STATUS,
+ RAN_MSG_LCLS_BREAK_REQ,
+ RAN_MSG_HANDOVER_COMMAND,
+ RAN_MSG_HANDOVER_PERFORMED,
+ RAN_MSG_HANDOVER_REQUIRED,
+ RAN_MSG_HANDOVER_REQUIRED_REJECT,
+ RAN_MSG_HANDOVER_REQUEST,
+ RAN_MSG_HANDOVER_REQUEST_ACK,
+ RAN_MSG_HANDOVER_DETECT,
+ RAN_MSG_HANDOVER_SUCCEEDED,
+ RAN_MSG_HANDOVER_COMPLETE,
+ RAN_MSG_HANDOVER_FAILURE,
+};
+
+extern const struct value_string ran_msg_type_names[];
+static inline const char *ran_msg_type_name(enum ran_msg_type val)
+{ return get_value_string(ran_msg_type_names, val); }
+
+struct ran_clear_command {
+ enum gsm0808_cause gsm0808_cause;
+ bool csfb_ind;
+};
+
+struct ran_assignment_command {
+ const struct osmo_sockaddr_str *cn_rtp;
+ const struct gsm0808_channel_type *channel_type;
+ enum nsap_addr_enc rab_assign_addr_enc;
+};
+
+struct ran_cipher_mode_command {
+ const struct osmo_auth_vector *vec;
+ const struct osmo_gsm48_classmark *classmark;
+ struct {
+ bool umts_aka;
+ bool retrieve_imeisv;
+ uint8_t a5_encryption_mask;
+
+ /* out-argument to return the key to the caller, pass NULL if not needed. */
+ struct geran_encr *chosen_key;
+ } geran;
+};
+
+struct ran_handover_request {
+ const char *imsi;
+ const struct osmo_gsm48_classmark *classmark;
+ /* A Handover Request on GERAN-A sends separate IEs for
+ * - permitted algorithms, here composed from the a5_encryption_mask,
+ * - the key, here taken from chosen_encryption->key iff chosen_encryption is present,
+ * - the actually chosen algorithm ("Serving"), here taken from chosen_encryption->alg_id.
+ */
+ struct {
+ struct gsm0808_channel_type *channel_type;
+ uint8_t a5_encryption_mask;
+ /*! chosen_encryption->alg_id is in encoded format:
+ * alg_id == 1 means A5/0 i.e. no encryption, alg_id == 4 means A5/3.
+ * alg_id == 0 means no such IE was present. */
+ struct geran_encr *chosen_encryption;
+ } geran;
+ struct gsm0808_cell_id cell_id_serving;
+ struct gsm0808_cell_id cell_id_target;
+
+ enum gsm0808_cause bssap_cause;
+
+ bool current_channel_type_1_present;
+ uint8_t current_channel_type_1;
+
+ enum gsm0808_permitted_speech speech_version_used;
+
+ const uint8_t *old_bss_to_new_bss_info_raw;
+ uint8_t old_bss_to_new_bss_info_raw_len;
+
+ struct osmo_sockaddr_str *rtp_ran_local;
+
+ struct gsm0808_speech_codec_list *codec_list_msc_preferred;
+
+ bool call_id_present;
+ uint32_t call_id;
+
+ const uint8_t *global_call_reference;
+ uint8_t global_call_reference_len;
+};
+
+struct ran_handover_request_ack {
+ const uint8_t *rr_ho_command;
+ uint8_t rr_ho_command_len;
+ bool chosen_channel_present;
+ uint8_t chosen_channel;
+ /*! chosen_encr_alg is in encoded format:
+ * chosen_encr_alg == 1 means A5/0 i.e. no encryption, chosen_encr_alg == 4 means A5/3.
+ * chosen_encr_alg == 0 means no such IE was present. */
+ uint8_t chosen_encr_alg;
+
+ /* chosen_speech_version == 0 means "not present" */
+ enum gsm0808_permitted_speech chosen_speech_version;
+
+ struct osmo_sockaddr_str remote_rtp;
+ bool codec_present;
+ enum mgcp_codecs codec;
+};
+
+struct ran_handover_command {
+ const uint8_t *rr_ho_command;
+ uint8_t rr_ho_command_len;
+
+ const uint8_t *new_bss_to_old_bss_info_raw;
+ uint8_t new_bss_to_old_bss_info_raw_len;
+};
+
+struct ran_handover_required {
+ uint16_t cause;
+ struct gsm0808_cell_id_list2 cil;
+
+ bool current_channel_type_1_present;
+ /*! See gsm0808_chosen_channel() */
+ uint8_t current_channel_type_1;
+
+ enum gsm0808_permitted_speech speech_version_used;
+
+ uint8_t *old_bss_to_new_bss_info_raw;
+ size_t old_bss_to_new_bss_info_raw_len;
+};
+
+struct ran_msg {
+ enum ran_msg_type msg_type;
+
+ /* Since different RAN implementations feed these messages, they should place here an implementation specific
+ * string constant to name the actual message (e.g. "BSSMAP Assignment Complete" vs. "RANAP RAB Assignment
+ * Response") */
+ const char *msg_name;
+
+ union {
+ struct {
+ const struct gsm0808_cell_id *cell_id;
+ struct msgb *msg;
+ } compl_l3;
+ struct msgb *dtap;
+ struct {
+ enum gsm0808_cause bssap_cause;
+#define RAN_MSG_BSSAP_CAUSE_UNSET 0xffff
+ } clear_request;
+ struct ran_clear_command clear_command;
+ struct {
+ const struct osmo_gsm48_classmark *classmark;
+ } classmark_update;
+ struct ran_cipher_mode_command cipher_mode_command;
+ struct {
+ /*! alg_id is in encoded format:
+ * alg_id == 1 means A5/0 i.e. no encryption, alg_id == 4 means A5/3.
+ * alg_id == 0 means no such IE was present. */
+ uint8_t alg_id;
+ const char *imeisv;
+ } cipher_mode_complete;
+ struct {
+ enum gsm0808_cause bssap_cause;
+ } cipher_mode_reject;
+ struct {
+ const char *imsi;
+ } common_id;
+ struct {
+ enum gsm48_reject_value cause;
+ } cm_service_reject;
+ struct ran_assignment_command assignment_command;
+ struct {
+ struct osmo_sockaddr_str remote_rtp;
+ bool codec_present;
+ enum mgcp_codecs codec;
+ } assignment_complete;
+ struct {
+ enum gsm0808_cause bssap_cause;
+ uint8_t rr_cause;
+ const struct gsm0808_speech_codec_list *scl_bss_supported;
+ } assignment_failure;
+ struct {
+ enum gsm0808_cause bssap_cause;
+ uint8_t dlci;
+ } sapi_n_reject;
+ struct {
+ enum gsm0808_lcls_status status;
+ } lcls_status;
+ struct {
+ int todo;
+ } lcls_break_req;
+ struct ran_handover_required handover_required;
+ struct gsm0808_handover_required_reject handover_required_reject;
+ struct ran_handover_command handover_command;
+ struct {
+ enum gsm0808_cause cause;
+ } handover_failure;
+ struct ran_handover_request handover_request;
+ struct ran_handover_request_ack handover_request_ack;
+ };
+};
+
+/* MSC-A/I/T roles implement this to receive decoded NAS messages, upon feeding an L2 msgb to a ran_dec_l2_t matching the
+ * RAN type implementation. */
+typedef int (* ran_decode_cb_t )(struct osmo_fsm_inst *caller_fi, void *caller_data, const struct ran_msg *msg);
+
+struct ran_dec {
+ /* caller provided osmo_fsm_inst, used both for logging from within decoding of NAS events, as well as caller's
+ * context in decode_cb(). */
+ struct osmo_fsm_inst *caller_fi;
+ void *caller_data;
+
+ /* Callback receives the decoded NAS messages */
+ ran_decode_cb_t decode_cb;
+};
+
+/* NAS decoders (BSSAP/RANAP) implement this to turn a msgb into a struct ran_msg.
+ * An implementation typically calls ran_decoded() when done decoding.
+ * NAS decoding is modeled with a callback instead of a plain decoding, because some L2 messages by design contain more
+ * than one NAS event, e.g. Ciphering Mode Complete may include another L3 message for Identity Response, and LCLS
+ * Information messages can contain Status and Break Req events. */
+typedef int (* ran_dec_l2_t )(struct ran_dec *ran_dec, struct msgb *l2);
+
+int ran_decoded(struct ran_dec *ran_dec, struct ran_msg *msg);
+
+/* An MSC-A/I/T role that receives NAS events containing DTAP buffers may use this to detect DTAP duplicates as in 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);
+
+/* Implemented by individual RAN implementations, see ran_a_encode() and ran_iu_encode(). */
+typedef struct msgb *(* ran_encode_t )(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg);
diff --git a/include/osmocom/msc/ran_msg_a.h b/include/osmocom/msc/ran_msg_a.h
new file mode 100644
index 000000000..3ba081de2
--- /dev/null
+++ b/include/osmocom/msc/ran_msg_a.h
@@ -0,0 +1,45 @@
+/* Abstraction of BSSAP decoding into NAS events, to be handled by MSC-A or MSC-I, and encoding of BSSAP messages
+ * towards the RAN. */
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/>.
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/paging.h>
+
+struct msgb;
+struct sccp_ran_inst;
+struct msub;
+struct gsm_mncc_bearer_cap;
+
+int ran_a_decode_l2(struct ran_dec *ran_a, struct msgb *bssap);
+struct msgb *ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg);
+
+enum reset_msg_type bssmap_is_reset_msg(const struct sccp_ran_inst *sri, const struct msgb *l2);
+struct msgb *bssmap_make_reset_msg(const struct sccp_ran_inst *sri, enum reset_msg_type type);
+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);
+const char *bssmap_msg_name(const struct sccp_ran_inst *sri, const struct msgb *l2);
+
+enum mgcp_codecs ran_a_mgcp_codec_from_sc(const struct gsm0808_speech_codec *sc);
+int ran_a_bearer_cap_to_channel_type(struct gsm0808_channel_type *ct, const struct gsm_mncc_bearer_cap *bc);
diff --git a/include/osmocom/msc/ran_msg_iu.h b/include/osmocom/msc/ran_msg_iu.h
new file mode 100644
index 000000000..316a91cdb
--- /dev/null
+++ b/include/osmocom/msc/ran_msg_iu.h
@@ -0,0 +1,35 @@
+/* Abstraction of RANAP decoding into NAS events, to be handled by MSC-A or MSC-I, and encoding of RANAP messages
+ * towards the RAN. */
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/>.
+ */
+#pragma once
+
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/paging.h>
+
+int ran_iu_decode_l2(struct ran_dec *ran_dec_iu, struct msgb *ranap);
+struct msgb *ran_iu_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg);
+
+enum reset_msg_type ranap_is_reset_msg(const struct sccp_ran_inst *sri, const struct msgb *l2);
+struct msgb *ranap_make_reset_msg(const struct sccp_ran_inst *sri, enum reset_msg_type type);
+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);
+const char *ranap_msg_name(const struct sccp_ran_inst *sri, const struct msgb *l2);
diff --git a/include/osmocom/msc/ran_peer.h b/include/osmocom/msc/ran_peer.h
new file mode 100644
index 000000000..e3ff59d9c
--- /dev/null
+++ b/include/osmocom/msc/ran_peer.h
@@ -0,0 +1,106 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/sigtran/sccp_sap.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/paging.h>
+
+struct vlr_subscr;
+struct ran_conn;
+struct neighbor_ident_entry;
+
+#define LOG_RAN_PEER_CAT(RAN_PEER, subsys, loglevel, fmt, args ...) \
+ LOGPFSMSL((RAN_PEER)? (RAN_PEER)->fi : NULL, subsys, loglevel, fmt, ## args)
+
+#define LOG_RAN_PEER(RAN_PEER, loglevel, fmt, args ...) \
+ LOG_RAN_PEER_CAT(RAN_PEER, \
+ (RAN_PEER) && (RAN_PEER)->sri? (RAN_PEER)->sri->ran->log_subsys : DMSC, \
+ loglevel, fmt, ## args)
+
+/* A BSC or RNC with activity on a local SCCP connection.
+ * Here we collect those BSC and RNC peers that are actually connected to the MSC and manage their connection Reset
+ * status.
+ *
+ * Before we had explicit neighbor configuration for inter-BSC and inter-MSC handover, the only way to know which peer
+ * address corresponds to which LAC (for paging a specific LAC) was to collect the LAC from L3 messages coming in on a
+ * subscriber connection. We still continue that practice to support unconfigured operation.
+ *
+ * The neighbor list config extends this by possibly naming LAC and CI that have not seen explicit activity yet, and
+ * allows us to page towards the correct peer's SCCP address from the start.
+ *
+ * So, for paging, the idea is to look for a LAC that is recorded here, and if not found, query the neighbor
+ * configuration for a peer's SCCP address matching that LAC. If found, look for active connections on that SCCP address
+ * here.
+ *
+ * Any valid RAN peer will contact us and initiate a RESET procedure. In turn, on osmo-msc start, we may choose to
+ * initiate a RESET procedure towards every known RAN peer.
+ *
+ * Semantically, it would make sense to keep the list of ran_conn instances in each struct ran_peer, but since
+ * non-Initial Connection-Oriented messages indicate only the conn by id (and identify the ran_peer from that), the conn
+ * list is kept in sccp_ran_inst. For convenience, see ran_peer_for_each_ran_conn().
+ */
+struct ran_peer {
+ /* Entry in sccp_ran_inst->ran_conns */
+ struct llist_head entry;
+
+ struct sccp_ran_inst *sri;
+ struct osmo_sccp_addr peer_addr;
+ struct osmo_fsm_inst *fi;
+
+ /* See cell_id_list.h */
+ struct llist_head cells_seen;
+};
+
+#define ran_peer_for_each_ran_conn(RAN_CONN, RAN_PEER) \
+ llist_for_each_entry(RAN_CONN, &(RAN_PEER)->sri->ran_conns, entry) \
+ if ((RAN_CONN)->ran_peer == (RAN_PEER))
+
+#define ran_peer_for_each_ran_conn_safe(RAN_CONN, RAN_CONN_NEXT, RAN_PEER) \
+ llist_for_each_entry_safe(RAN_CONN, RAN_CONN_NEXT, &(RAN_PEER)->sri->ran_conns, entry) \
+ if ((RAN_CONN)->ran_peer == (RAN_PEER))
+
+enum ran_peer_state {
+ RAN_PEER_ST_WAIT_RX_RESET = 0,
+ RAN_PEER_ST_WAIT_RX_RESET_ACK,
+ RAN_PEER_ST_READY,
+ RAN_PEER_ST_DISCARDING,
+};
+
+enum ran_peer_event {
+ RAN_PEER_EV_MSG_UP_CL = 0,
+ RAN_PEER_EV_MSG_UP_CO_INITIAL,
+ RAN_PEER_EV_MSG_UP_CO,
+ RAN_PEER_EV_MSG_DOWN_CL,
+ RAN_PEER_EV_MSG_DOWN_CO_INITIAL,
+ RAN_PEER_EV_MSG_DOWN_CO,
+ RAN_PEER_EV_RX_RESET,
+ RAN_PEER_EV_RX_RESET_ACK,
+ RAN_PEER_EV_CONNECTION_SUCCESS,
+ RAN_PEER_EV_CONNECTION_TIMEOUT,
+};
+
+struct ran_peer_ev_ctx {
+ uint32_t conn_id;
+ struct ran_conn *conn;
+ struct msgb *msg;
+};
+
+struct ran_peer *ran_peer_find_or_create(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr);
+struct ran_peer *ran_peer_find(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr);
+
+void ran_peer_cells_seen_add(struct ran_peer *ran_peer, const struct gsm0808_cell_id *id);
+
+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);
+void ran_peer_disconnect(struct sccp_ran_inst *sri, uint32_t conn_id);
+
+int ran_peers_down_paging(struct sccp_ran_inst *sri, enum CELL_IDENT page_where, struct vlr_subscr *vsub,
+ enum paging_cause cause);
+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 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 *ran_peer_find_by_addr(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *addr);
diff --git a/include/osmocom/msc/rtp_stream.h b/include/osmocom/msc/rtp_stream.h
new file mode 100644
index 000000000..794e8066f
--- /dev/null
+++ b/include/osmocom/msc/rtp_stream.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+struct gsm_trans;
+
+struct osmo_fsm_inst;
+struct call_leg;
+struct osmo_mgcpc_ep;
+struct osmo_mgcpc_ep_ci;
+
+enum rtp_direction {
+ RTP_TO_RAN,
+ RTP_TO_CN,
+};
+
+extern const struct value_string rtp_direction_names[];
+static inline const char *rtp_direction_name(enum rtp_direction val)
+{ return get_value_string(rtp_direction_names, val); }
+
+/* A single bidirectional RTP hop between remote and MGW's local RTP port. */
+struct rtp_stream {
+ struct osmo_fsm_inst *fi;
+ struct call_leg *parent_call_leg;
+ enum rtp_direction dir;
+
+ uint32_t call_id;
+
+ /* Backpointer for callers (optional) */
+ struct gsm_trans *for_trans;
+
+ struct osmo_sockaddr_str local;
+ struct osmo_sockaddr_str remote;
+ bool remote_sent_to_mgw;
+
+ bool codec_known;
+ enum mgcp_codecs codec;
+ bool codec_sent_to_mgw;
+
+ struct osmo_mgcpc_ep_ci *ci;
+
+ enum mgcp_connection_mode crcx_conn_mode;
+};
+
+#define RTP_STREAM_FMT "local=" RTP_IP_PORT_FMT ",remote=" RTP_IP_PORT_FMT
+#define RTP_STREAM_ARGS(RS) RTP_IP_PORT_ARGS(&(RS)->local), RTP_IP_PORT_ARGS(&(RS)->remote),
+
+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);
+
+int rtp_stream_ensure_ci(struct rtp_stream *rtps, struct osmo_mgcpc_ep *at_endpoint);
+int rtp_stream_do_mdcx(struct rtp_stream *rtps);
+
+void rtp_stream_set_codec(struct rtp_stream *rtps, enum mgcp_codecs codec);
+void rtp_stream_set_remote_addr(struct rtp_stream *rtps, const struct osmo_sockaddr_str *r);
+int rtp_stream_commit(struct rtp_stream *rtps);
+
+void rtp_stream_release(struct rtp_stream *rtps);
+
+bool rtp_stream_is_established(struct rtp_stream *rtps);
diff --git a/include/osmocom/msc/sccp_ran.h b/include/osmocom/msc/sccp_ran.h
new file mode 100644
index 000000000..b7da314b2
--- /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);
diff --git a/include/osmocom/msc/sgs_iface.h b/include/osmocom/msc/sgs_iface.h
index a167cd6d8..575468e10 100644
--- a/include/osmocom/msc/sgs_iface.h
+++ b/include/osmocom/msc/sgs_iface.h
@@ -24,8 +24,11 @@
#include <osmocom/gsm/protocol/gsm_29_118.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/vlr_sgs.h>
+#include <osmocom/msc/paging.h>
#include <osmocom/core/socket.h>
+struct msc_a;
+
static const unsigned int sgs_state_timer_defaults[_NUM_SGS_STATE_TIMERS] = {
[SGS_STATE_TS5] = SGS_TS5_DEFAULT,
[SGS_STATE_TS6_2] = SGS_TS6_2_DEFAULT,
@@ -82,6 +85,8 @@ extern struct sgs_state *g_sgs;
struct sgs_state *sgs_iface_init(void *ctx, struct gsm_network *network);
int sgs_iface_rx(struct sgs_connection *sgc, struct msgb *msg);
+enum sgsap_service_ind sgs_serv_ind_from_paging_cause(enum paging_cause);
int sgs_iface_tx_paging(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind);
-int sgs_iface_tx_dtap_ud(struct msgb *msg);
-void sgs_iface_tx_release(struct ran_conn *conn);
+int sgs_iface_tx_dtap_ud(struct msc_a *msc_a, struct msgb *msg);
+void sgs_iface_tx_release(struct vlr_subscr *vsub);
+
diff --git a/include/osmocom/msc/signal.h b/include/osmocom/msc/signal.h
index 51269763e..16b5678db 100644
--- a/include/osmocom/msc/signal.h
+++ b/include/osmocom/msc/signal.h
@@ -28,6 +28,9 @@
#include <osmocom/core/signal.h>
+struct msc_a;
+struct vty;
+
/*
* Signalling subsystems
*/
@@ -63,7 +66,7 @@ enum signal_subscr {
/* SS_SCALL signals */
enum signal_scall {
S_SCALL_SUCCESS,
- S_SCALL_EXPIRED,
+ S_SCALL_FAILED,
S_SCALL_DETACHED,
};
@@ -78,23 +81,18 @@ enum signal_global {
struct paging_signal_data {
struct vlr_subscr *vsub;
- struct gsm_bts *bts;
-
- int paging_result;
-
- /* NULL in case the paging didn't work */
- struct ran_conn *conn;
+ struct msc_a *msc_a;
};
struct scall_signal_data {
- struct ran_conn *conn;
- void *data;
+ struct msc_a *msc_a;
+ struct vty *vty;
};
struct sms_signal_data {
/* The transaction where this occured */
struct gsm_trans *trans;
/* Can be NULL for SMMA */
struct gsm_sms *sms;
- /* int paging result. Only the ones with > 0 */
- int paging_result;
+ /* true when paging was successful */
+ bool paging_result;
};
diff --git a/include/osmocom/msc/silent_call.h b/include/osmocom/msc/silent_call.h
index ca36052ff..fb53e9049 100644
--- a/include/osmocom/msc/silent_call.h
+++ b/include/osmocom/msc/silent_call.h
@@ -1,15 +1,18 @@
#ifndef _SILENT_CALL_H
#define _SILENT_CALL_H
-struct ran_conn;
struct gsm0808_channel_type;
+struct gsm_trans;
+
+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,
+ struct vty *vty);
-extern 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);
extern int gsm_silent_call_stop(struct vlr_subscr *vsub);
+void trans_silent_call_free(struct gsm_trans *trans);
+
#if 0
extern int silent_call_rx(struct ran_conn *conn, struct msgb *msg);
extern int silent_call_reroute(struct ran_conn *conn, struct msgb *msg);
diff --git a/include/osmocom/msc/sms_queue.h b/include/osmocom/msc/sms_queue.h
index 70cabe287..ef73baf04 100644
--- a/include/osmocom/msc/sms_queue.h
+++ b/include/osmocom/msc/sms_queue.h
@@ -6,6 +6,7 @@ struct gsm_sms_queue;
struct vty;
#define VSUB_USE_SMS_PENDING "SMS-pending"
+#define MSC_A_USE_SMS_PENDING "SMS-pending"
int sms_queue_start(struct gsm_network *, int in_flight);
int sms_queue_trigger(struct gsm_sms_queue *);
diff --git a/include/osmocom/msc/transaction.h b/include/osmocom/msc/transaction.h
index 7ffcf3b78..99aca55ef 100644
--- a/include/osmocom/msc/transaction.h
+++ b/include/osmocom/msc/transaction.h
@@ -6,18 +6,21 @@
#include <osmocom/core/fsm.h>
#include <osmocom/msc/gsm_04_11.h>
#include <osmocom/msc/mncc.h>
+#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/debug.h>
#include <osmocom/gsm/gsm0411_smc.h>
#include <osmocom/gsm/gsm0411_smr.h>
+struct vty;
+
/* Used for late TID assignment */
#define TRANS_ID_UNASSIGNED 0xff
#define LOG_TRANS_CAT(trans, subsys, level, fmt, args...) \
LOGP(subsys, level, \
"trans(%s %s callref-0x%x tid-%u%s) " fmt, \
- (trans) ? gsm48_pdisc_name((trans)->protocol) : "NULL", \
- (trans) ? ((trans)->conn ? (trans)->conn->fi->id : vlr_subscr_name((trans)->vsub)) : "NULL", \
+ (trans) ? trans_type_name((trans)->type) : "NULL", \
+ (trans) ? ((trans)->msc_a ? (trans)->msc_a->c.fi->id : vlr_subscr_name((trans)->vsub)) : "NULL", \
(trans) ? (trans)->callref : 0, \
(trans) ? (trans)->transaction_id : 0, \
(trans) && (trans)->paging_request ? ",PAGING" : "", \
@@ -34,6 +37,19 @@ enum bridge_state {
BRIDGE_STATE_BRIDGE_ESTABLISHED,
};
+enum trans_type {
+ TRANS_CC = GSM48_PDISC_CC,
+ TRANS_SMS = GSM48_PDISC_SMS,
+ TRANS_USSD = GSM48_PDISC_NC_SS,
+ TRANS_SILENT_CALL,
+};
+
+extern const struct value_string trans_type_names[];
+static inline const char *trans_type_name(enum trans_type val)
+{ return get_value_string(trans_type_names, val); }
+
+uint8_t trans_type_to_gsm48_proto(enum trans_type type);
+
/* One transaction */
struct gsm_trans {
/* Entry in list of all transactions */
@@ -42,8 +58,8 @@ struct gsm_trans {
/* Back pointer to the network struct */
struct gsm_network *net;
- /* The protocol within which we live */
- uint8_t protocol;
+ /* What kind of transaction */
+ enum trans_type type;
/* The current transaction ID */
uint8_t transaction_id;
@@ -55,7 +71,7 @@ struct gsm_trans {
struct vlr_subscr *vsub;
/* The associated connection we are using to transmit messages */
- struct ran_conn *conn;
+ struct msc_a *msc_a;
/* reference from MNCC or other application */
uint32_t callref;
@@ -64,7 +80,7 @@ struct gsm_trans {
int tch_recv;
/* is thats one paging? */
- struct subscr_request *paging_request;
+ struct paging_request *paging_request;
/* bearer capabilities (rate and codec) */
struct gsm_mncc_bearer_cap bearer_cap;
@@ -85,7 +101,6 @@ struct gsm_trans {
struct osmo_timer_list timer;
struct osmo_timer_list timer_guard;
struct gsm_mncc msg; /* stores setup/disconnect/release message */
- bool assignment_started;
} cc;
struct {
struct gsm411_smc_inst smc_inst;
@@ -105,6 +120,11 @@ struct gsm_trans {
/* Inactivity timer, triggers transaction release */
struct osmo_timer_list timer_guard;
} ss;
+ struct {
+ struct gsm0808_channel_type ct;
+ struct osmo_sockaddr_str rtp_cn;
+ struct vty *from_vty;
+ } silent_call;
};
struct {
@@ -115,8 +135,9 @@ struct gsm_trans {
-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_type(const struct msc_a *msc_a, enum trans_type type);
+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_find_by_callref(const struct gsm_network *net,
uint32_t callref);
struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net,
@@ -125,26 +146,28 @@ struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net,
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);
void trans_free(struct gsm_trans *trans);
int trans_assign_trans_id(const struct gsm_network *net, const struct vlr_subscr *vsub,
- uint8_t protocol);
-struct gsm_trans *trans_has_conn(const struct ran_conn *conn);
-void trans_conn_closed(const struct ran_conn *conn);
+ enum trans_type type);
+struct gsm_trans *trans_has_conn(const struct msc_a *msc_a);
+void trans_conn_closed(const struct msc_a *msc_a);
static inline int trans_log_subsys(const struct gsm_trans *trans)
{
if (!trans)
return DMSC;
- switch (trans->protocol) {
- case GSM48_PDISC_CC:
+ switch (trans->type) {
+ case TRANS_CC:
return DCC;
- case GSM48_PDISC_SMS:
+ case TRANS_SMS:
return DLSMS;
default:
break;
}
+ if (trans->msc_a)
+ return trans->msc_a->c.ran->log_subsys;
return DMSC;
}
diff --git a/include/osmocom/msc/vlr.h b/include/osmocom/msc/vlr.h
index ce6a232fa..4c119514a 100644
--- a/include/osmocom/msc/vlr.h
+++ b/include/osmocom/msc/vlr.h
@@ -91,11 +91,6 @@ struct vlr_auth_tuple {
#define VLR_KEY_SEQ_INVAL 7 /* GSM 04.08 - 10.5.1.2 */
-struct vlr_ciph_result {
- enum vlr_ciph_result_cause cause;
- char imeisv[GSM48_MI_SIZE];
-};
-
enum vlr_subscr_security_context {
VLR_SEC_CTX_NONE,
VLR_SEC_CTX_GSM,
@@ -162,7 +157,8 @@ struct vlr_subscr {
bool la_allowed;
struct osmo_use_count use_count;
- struct osmo_use_count_entry use_count_buf[10];
+ struct osmo_use_count_entry use_count_buf[8];
+ int32_t max_total_use_count;
struct osmo_fsm_inst *lu_fsm;
struct osmo_fsm_inst *auth_fsm;
@@ -200,20 +196,19 @@ struct vlr_subscr {
struct osmo_timer_list Ts5;
} sgs;
- struct gsm_classmark classmark;
+ struct osmo_gsm48_classmark classmark;
};
enum vlr_ciph {
- VLR_CIPH_NONE, /*< A5/0, no encryption */
- VLR_CIPH_A5_1, /*< A5/1, encryption */
- VLR_CIPH_A5_2, /*< A5/2, deprecated export-grade encryption */
- VLR_CIPH_A5_3, /*< A5/3, 'new secure' encryption */
+ VLR_CIPH_NONE = 0, /*< A5/0, no encryption */
+ VLR_CIPH_A5_1 = 1, /*< A5/1, encryption */
+ VLR_CIPH_A5_2 = 2, /*< A5/2, deprecated export-grade encryption */
+ VLR_CIPH_A5_3 = 3, /*< A5/3, 'new secure' encryption */
};
static inline uint8_t vlr_ciph_to_gsm0808_alg_id(enum vlr_ciph ciph)
{
switch (ciph) {
- default:
case VLR_CIPH_NONE:
return GSM0808_ALG_ID_A5_0;
case VLR_CIPH_A5_1:
@@ -222,6 +217,8 @@ static inline uint8_t vlr_ciph_to_gsm0808_alg_id(enum vlr_ciph ciph)
return GSM0808_ALG_ID_A5_2;
case VLR_CIPH_A5_3:
return GSM0808_ALG_ID_A5_3;
+ default:
+ return GSM0808_ALG_ID_A5_7;
}
}
@@ -240,12 +237,12 @@ struct vlr_ops {
int (*tx_lu_acc)(void *msc_conn_ref, uint32_t send_tmsi);
int (*tx_lu_rej)(void *msc_conn_ref, enum gsm48_reject_value cause);
- int (*tx_cm_serv_acc)(void *msc_conn_ref);
- int (*tx_cm_serv_rej)(void *msc_conn_ref, enum gsm48_reject_value cause);
+ int (*tx_cm_serv_acc)(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type);
+ int (*tx_cm_serv_rej)(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type,
+ enum gsm48_reject_value cause);
int (*set_ciph_mode)(void *msc_conn_ref, bool umts_aka, bool retrieve_imeisv);
- /* UTRAN: send Common Id (when auth+ciph are complete) */
int (*tx_common_id)(void *msc_conn_ref);
int (*tx_mm_info)(void *msc_conn_ref);
@@ -255,9 +252,6 @@ struct vlr_ops {
/* notify MSC/SGSN that the given subscriber has been associated
* with this msc_conn_ref */
int (*subscr_assoc)(void *msc_conn_ref, struct vlr_subscr *vsub);
-
- /* Forward a parsed GSUP message towards MSC message router */
- int (*forward_gsup_msg)(struct vlr_subscr *vsub, struct osmo_gsup_message *gsup_msg);
};
enum vlr_timer {
@@ -271,7 +265,7 @@ enum vlr_timer {
struct vlr_instance {
struct llist_head subscribers;
struct llist_head operations;
- struct osmo_gsup_client *gsup_client;
+ struct gsup_client_mux *gcm;
struct vlr_ops ops;
struct osmo_timer_list lu_expire_timer;
struct {
@@ -323,13 +317,13 @@ int vlr_subscr_rx_auth_resp(struct vlr_subscr *vsub, bool is_r99, bool is_utran,
const uint8_t *res, uint8_t res_len);
int vlr_subscr_rx_auth_fail(struct vlr_subscr *vsub, const uint8_t *auts);
int vlr_subscr_tx_auth_fail_rep(const struct vlr_subscr *vsub) __attribute__((warn_unused_result));
-void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, struct vlr_ciph_result *res);
+void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, enum vlr_ciph_result_cause result);
int vlr_subscr_rx_tmsi_reall_compl(struct vlr_subscr *vsub);
int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub);
struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops);
-int vlr_start(struct ipaccess_unit *ipa_dev, struct vlr_instance *vlr,
- const char *gsup_server_addr_str, uint16_t gsup_server_port);
+int vlr_start(struct vlr_instance *vlr, struct gsup_client_mux *gcm);
+int vlr_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg);
/* internal use only */
@@ -351,6 +345,7 @@ lu_compl_vlr_proc_start(struct osmo_fsm_inst *parent,
const char *vlr_subscr_name(const struct vlr_subscr *vsub);
+const char *vlr_subscr_short_name(const struct vlr_subscr *vsub, unsigned int maxlen);
const char *vlr_subscr_msisdn_or_name(const struct vlr_subscr *vsub);
#define vlr_subscr_find_by_imsi(vlr, imsi, USE) \
@@ -454,7 +449,8 @@ vlr_proc_acc_req(struct osmo_fsm_inst *parent,
uint32_t parent_event_failure,
void *parent_event_data,
struct vlr_instance *vlr, void *msc_conn_ref,
- enum vlr_parq_type type, const uint8_t *mi_lv,
+ enum vlr_parq_type type, enum osmo_cm_service_type cm_service_type,
+ const uint8_t *mi_lv,
const struct osmo_location_area_id *lai,
bool authentication_required,
bool ciphering_required,
diff --git a/include/osmocom/msc/vlr_sgs.h b/include/osmocom/msc/vlr_sgs.h
index 1cbb771af..00d52f7b4 100644
--- a/include/osmocom/msc/vlr_sgs.h
+++ b/include/osmocom/msc/vlr_sgs.h
@@ -27,7 +27,7 @@ struct vlr_subscr;
struct vlr_instance;
#define VSUB_USE_SGS "SGs"
-#define VSUB_USE_SGS_PAGING "SGs-paging"
+#define VSUB_USE_SGS_PAGING_REQ "SGs-paging-req"
/* See also 3GPP TS 29.118, chapter 4.2.2 States at the VLR */
enum sgs_ue_fsm_state {
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..b10f176c2
--- /dev/null
+++ b/src/libmsc/call_leg.c
@@ -0,0 +1,356 @@
+/* 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_zero(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:
+ 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;
+ if (cl->fi->state != CALL_LEG_ST_ESTABLISHED)
+ 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:
+ break;
+ }
+}
+
+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, /* same action function as above */
+ },
+};
+
+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),
+ {}
+};
+
+static int call_leg_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);
+ return 0;
+}
+
+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]) {
+ if (call_leg_rtp_alloc(cl, dir, call_id, for_trans))
+ return -EIO;
+ }
+ 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..6e59e1667
--- /dev/null
+++ b/src/libmsc/cell_id_list.c
@@ -0,0 +1,76 @@
+/* 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_zero(talloc_ctx, struct cell_id_list_entry);
+ e->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..261413766
--- /dev/null
+++ b/src/libmsc/e_link.c
@@ -0,0 +1,380 @@
+/* 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;
+ size_t use_len;
+
+ /* 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 = (struct e_link) {
+ .gcm = gcm,
+ };
+
+ /* FIXME: this is a braindamaged duality of char* and blob, which we can't seem to get rid of easily.
+ * See also osmo-hlr change I01a45900e14d41bcd338f50ad85d9fabf2c61405 which resolved this on the
+ * osmo-hlr side, but was abandoned. Not sure which way is the right solution. */
+ /* To be able to include a terminating NUL character when sending the IPA name, append one if there is none yet.
+ * Current osmo-hlr needs the terminating NUL to be included. */
+ use_len = remote_name_len;
+ if (remote_name[use_len-1] != '\0')
+ use_len++;
+ e->remote_name = talloc_size(e, use_len);
+ memcpy(e->remote_name, remote_name, remote_name_len);
+ e->remote_name[use_len-1] = '\0';
+ e->remote_name_len = use_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, enum msc_role from_role, 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)+1, /* include terminating nul */
+ .destination_name = (const uint8_t*)e->remote_name,
+ .destination_name_len = e->remote_name_len, /* the nul here is also included, from e_link_alloc() */
+ };
+
+ 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) {
+ LOG_MSUB_CAT(msub, 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..6236bb331 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:
@@ -895,16 +847,17 @@ static int gsm48_rx_mm_imsi_detach_ind(struct ran_conn *conn, struct msgb *msg)
break;
}
+
if (!vsub) {
LOGP(DMM, LOGL_ERROR, "IMSI DETACH for unknown subscriber MI(%s)=%s\n",
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 +865,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 +879,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 +897,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 +907,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 +917,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 +943,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"
+ 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",
- vlr_subscr_name(conn->vsub), is_umts ? "UMTS" : "GSM", rc);
+ 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 +1017,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 +1029,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 +1110,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 +1211,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 +1251,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 +1264,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 +1273,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 +1378,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 71069dc56..4a668b940 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)
@@ -575,7 +557,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, "
@@ -589,9 +571,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);
/*
@@ -820,7 +799,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];
@@ -1001,11 +981,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;
@@ -1015,9 +995,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;
}
@@ -1049,22 +1044,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;
@@ -1193,8 +1188,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;
@@ -1203,12 +1197,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?
@@ -1222,7 +1214,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;
@@ -1230,9 +1223,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",
@@ -1253,7 +1243,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;
@@ -1264,8 +1254,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);
@@ -1293,19 +1281,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..a9181546f
--- /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_zero(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..ce07e4a5f
--- /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, from_role, &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..86c7b9c54
--- /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, MSC_ROLE_A, &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);
-