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
This commit is contained in:
Neels Hofmeyr 2018-12-07 14:47:34 +01:00
parent 56f90132b8
commit c4628a3ad4
151 changed files with 35671 additions and 22996 deletions

View File

@ -256,6 +256,7 @@ AC_OUTPUT(
doc/Makefile doc/Makefile
doc/examples/Makefile doc/examples/Makefile
doc/manuals/Makefile doc/manuals/Makefile
doc/sequence_charts/Makefile
contrib/Makefile contrib/Makefile
contrib/systemd/Makefile contrib/systemd/Makefile
Makefile) Makefile)

View File

@ -1,4 +1,5 @@
SUBDIRS = \ SUBDIRS = \
examples \ examples \
manuals \ manuals \
sequence_charts \
$(NULL) $(NULL)

View File

@ -0,0 +1,35 @@
all:
echo "built only on manual invocation, needs mscgen and dot (graphviz) programs: invoke 'make charts'"
charts: msc dot
EXTRA_DIST = \
inter_bsc_ho.msc \
inter_msc_ho.msc \
mncc_fsm.msc \
$(NULL)
CLEANFILES = \
inter_bsc_ho.png \
inter_msc_ho.png \
mncc_fsm.png \
$(NULL)
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

View File

@ -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"];
}

View File

@ -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"];
}

View File

@ -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"];
}

View File

@ -1,8 +1,9 @@
noinst_HEADERS = \ noinst_HEADERS = \
a_iface.h \ call_leg.h \
a_iface_bssap.h \ cell_id_list.h \
db.h \ db.h \
debug.h \ debug.h \
e_link.h \
gsm_04_08.h \ gsm_04_08.h \
gsm_04_11.h \ gsm_04_11.h \
gsm_04_11_gsup.h \ gsm_04_11_gsup.h \
@ -12,17 +13,31 @@ noinst_HEADERS = \
gsm_data.h \ gsm_data.h \
gsm_data_shared.h \ gsm_data_shared.h \
gsm_subscriber.h \ gsm_subscriber.h \
iucs.h \ gsup_client_mux.h \
iucs_ranap.h \
iu_dummy.h \
mncc.h \ mncc.h \
mncc_call.h \
mncc_int.h \ mncc_int.h \
msc_a.h \
msc_a_remote.h \
msc_common.h \ msc_common.h \
msc_ifaces.h \ msc_ho.h \
msc_mgcp.h \ msc_i.h \
a_reset.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_conn.h \
ran_infra.h \
ran_msg.h \
ran_msg_a.h \
ran_msg_iu.h \
ran_peer.h \
rrlp.h \ rrlp.h \
rtp_stream.h \
sccp_ran.h \
sgs_iface.h \ sgs_iface.h \
sgs_server.h \ sgs_server.h \
sgs_vty.h \ sgs_vty.h \

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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, 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);

View File

@ -4,6 +4,7 @@
#include <osmocom/gsm/gsm48.h> #include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/msc/transaction.h>
struct msgb; struct msgb;
struct gsm_bts; struct gsm_bts;
@ -12,6 +13,7 @@ struct gsm_trans;
struct ran_conn; struct ran_conn;
struct amr_multirate_conf; struct amr_multirate_conf;
struct amr_mode; struct amr_mode;
struct msc_a;
#define GSM48_ALLOC_SIZE 2048 #define GSM48_ALLOC_SIZE 2048
#define GSM48_ALLOC_HEADROOM 256 #define GSM48_ALLOC_HEADROOM 256
@ -22,33 +24,26 @@ static inline struct msgb *gsm48_msgb_alloc_name(const char *name)
name); name);
} }
void cm_service_request_concludes(struct ran_conn *conn, void cm_service_request_concludes(struct msc_a *msc_a, struct msgb *msg, enum osmo_cm_service_type type);
struct msgb *msg);
/* config options controlling the behaviour of the lower leves */ /* config options controlling the behaviour of the lower leves */
void gsm0408_clear_all_trans(struct gsm_network *net, int protocol); void gsm0408_clear_all_trans(struct gsm_network *net, enum trans_type type);
int gsm0408_dispatch(struct ran_conn *conn, struct msgb *msg);
int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id); int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id);
/* don't use "enum gsm_chreq_reason_t" to avoid circular dependency */ /* don't use "enum gsm_chreq_reason_t" to avoid circular dependency */
void gsm_net_update_ctype(struct gsm_network *net); void gsm_net_update_ctype(struct gsm_network *net);
int gsm48_tx_simple(struct ran_conn *conn, int gsm48_tx_simple(struct msc_a *msc_a, uint8_t pdisc, uint8_t msg_type);
uint8_t pdisc, uint8_t msg_type); int gsm48_tx_mm_info(struct msc_a *msc_a);
int gsm48_tx_mm_info(struct ran_conn *conn); 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_req(struct ran_conn *conn, uint8_t *rand, int gsm48_tx_mm_auth_rej(struct msc_a *msc_a);
uint8_t *autn, int key_seq); int gsm48_tx_mm_serv_ack(struct msc_a *msc_a);
int gsm48_tx_mm_auth_rej(struct ran_conn *conn); int gsm48_tx_mm_serv_rej(struct msc_a *msc_a, enum gsm48_reject_value value);
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_send_rr_release(struct gsm_lchan *lchan); 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_ciph_mode(struct gsm_lchan *lchan, int want_imeisv);
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);
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_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, int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan, uint8_t power_command, uint8_t ho_ref);
uint8_t power_command, uint8_t ho_ref);
int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg); 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); 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); 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 #endif

View File

@ -7,6 +7,7 @@
struct vlr_subscr; struct vlr_subscr;
struct ran_conn; struct ran_conn;
struct gsm_trans; struct gsm_trans;
struct msc_a;
#define UM_SAPI_SMS 3 /* See GSM 04.05/04.06 */ #define UM_SAPI_SMS 3 /* See GSM 04.05/04.06 */
@ -31,7 +32,7 @@ struct sms_deliver {
struct gsm_network; struct gsm_network;
struct msgb; 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); struct gsm_sms *sms_alloc(void);
void sms_free(struct gsm_sms *sms); 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_oa_len, const uint8_t *sm_rp_oa,
size_t sm_rp_ud_len, const uint8_t *sm_rp_ud); 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_ack(struct gsm_trans *trans, uint8_t msg_ref);
int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref, int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref,

View File

@ -2,6 +2,7 @@
#include <stdint.h> #include <stdint.h>
struct gsup_client_mux;
struct osmo_gsup_message; struct osmo_gsup_message;
struct vlr_subscr; struct vlr_subscr;
struct gsm_trans; 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_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, 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); 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_res(struct gsm_trans *trans, uint8_t sm_rp_mr);
int gsm411_gsup_mt_fwd_sm_err(struct gsm_trans *trans, int gsm411_gsup_mt_fwd_sm_err(struct gsm_trans *trans,
uint8_t sm_rp_mr, uint8_t cause); 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);

View File

@ -2,14 +2,16 @@
#include <osmocom/gsm/protocol/gsm_04_14.h> #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); enum gsm414_tch_loop_mode loop_mode);
int gsm0414_tx_open_loop_cmd(struct ran_conn *conn); int gsm0414_tx_open_loop_cmd(struct msc_a *msc_a);
int gsm0414_tx_act_emmi_cmd(struct ran_conn *conn); int gsm0414_tx_act_emmi_cmd(struct msc_a *msc_a);
int gsm0414_tx_test_interface(struct ran_conn *conn, int gsm0414_tx_test_interface(struct msc_a *msc_a,
uint8_t tested_devs); 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); uint8_t technology);
int gsm0414_rcv_test(struct ran_conn *conn, int gsm0414_rcv_test(struct msc_a *msc_a,
struct msgb *msg); struct msgb *msg);

View File

@ -2,16 +2,13 @@
#include <stdint.h> #include <stdint.h>
struct ran_conn; struct msc_a;
int msc_send_ussd_reject(struct ran_conn *conn, int msc_send_ussd_reject(struct msc_a *msc_a, uint8_t transaction_id, int invoke_id,
uint8_t transaction_id, int invoke_id, uint8_t problem_tag, uint8_t problem_code);
uint8_t problem_tag, uint8_t problem_code);
int msc_send_ussd_notify(struct ran_conn *conn, int level, int msc_send_ussd_notify(struct msc_a *msc_a, int level, const char *text);
const char *text); int msc_send_ussd_release_complete(struct msc_a *msc_a, uint8_t transaction_id);
int msc_send_ussd_release_complete(struct ran_conn *conn, int msc_send_ussd_release_complete_cause(struct msc_a *msc_a,
uint8_t transaction_id);
int msc_send_ussd_release_complete_cause(struct ran_conn *conn,
uint8_t transaction_id, uint8_t transaction_id,
uint8_t cause_loc, uint8_t cause_val); uint8_t cause_loc, uint8_t cause_val);

View File

@ -1,7 +1,9 @@
#pragma once #pragma once
#include <osmocom/core/msgb.h> struct msc_a;
#include <osmocom/gsm/gsup.h> struct mgsb;
struct gsup_client_mux;
struct osmo_gsup_message;
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);
int gsm0911_gsup_handler(struct vlr_subscr *vsub, struct osmo_gsup_message *gsup); int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *msg);

View File

@ -16,23 +16,17 @@
#include <osmocom/mgcp_client/mgcp_client.h> #include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/msc/msc_common.h> #include <osmocom/msc/msc_common.h>
#include <osmocom/msc/neighbor_ident.h>
#include "gsm_data_shared.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 */ /** annotations for msgb ownership */
#define __uses #define __uses
struct mncc_sock_state; struct mncc_sock_state;
struct vlr_instance; struct vlr_instance;
struct vlr_subscr; struct vlr_subscr;
struct gsup_client_mux;
#define tmsi_from_string(str) strtoul(str, NULL, 10) #define tmsi_from_string(str) strtoul(str, NULL, 10)
@ -144,6 +138,7 @@ struct gsm_network {
struct mncc_sock_state *mncc_state; struct mncc_sock_state *mncc_state;
mncc_recv_cb_t mncc_recv; mncc_recv_cb_t mncc_recv;
struct llist_head upqueue; struct llist_head upqueue;
struct osmo_tdef *mncc_tdefs;
/* /*
* TODO: Move the trans_list into the RAN connection and * TODO: Move the trans_list into the RAN connection and
* create a pending list for MT transactions. These exist before * create a pending list for MT transactions. These exist before
@ -171,9 +166,6 @@ struct gsm_network {
/* control interface */ /* control interface */
struct ctrl_handle *ctrl; 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 /* if override is nonzero, this timezone data is used for all MM
* contexts. */ * contexts. */
/* TODO: in OsmoNITB, tz-override used to be BTS-specific. To enable /* 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 */ /* MSC: GSUP server address of the HLR */
const char *gsup_server_addr_str; const char *gsup_server_addr_str;
uint16_t gsup_server_port; uint16_t gsup_server_port;
struct gsup_client_mux *gcm;
struct vlr_instance *vlr; struct vlr_instance *vlr;
@ -196,28 +189,30 @@ struct gsm_network {
int ncss_guard_timeout; int ncss_guard_timeout;
struct { struct {
struct osmo_tdef *tdefs;
struct mgcp_client_conf conf; struct mgcp_client_conf conf;
struct mgcp_client *client; struct mgcp_client *client;
} mgw; } mgw;
#if BUILD_IU
struct { struct {
/* CS7 instance id number (set via VTY) */ /* CS7 instance id number (set via VTY) */
uint32_t cs7_instance; uint32_t cs7_instance;
enum ranap_nsap_addr_enc rab_assign_addr_enc; enum nsap_addr_enc rab_assign_addr_enc;
struct osmo_sccp_instance *sccp;
struct sccp_ran_inst *sri;
} iu; } iu;
#endif
struct { struct {
/* CS7 instance id number (set via VTY) */ /* CS7 instance id number (set via VTY) */
uint32_t cs7_instance; uint32_t cs7_instance;
/* A list with the context information about
* all BSCs we have connections with */ struct sccp_ran_inst *sri;
struct llist_head bscs;
struct osmo_sccp_instance *sccp;
} a; } 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 { struct {
/* MSISDN to which to route MO emergency calls */ /* MSISDN to which to route MO emergency calls */
char *route_to_msisdn; 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, * 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' */ * and will be of the form 'MSC-00-00-00-00-00-00' */
char *msc_ipa_name; 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; struct osmo_esme;

View File

@ -31,10 +31,4 @@ enum gsm_hooks {
GSM_HOOK_RR_SECURITY, GSM_HOOK_RR_SECURITY,
}; };
enum gsm_paging_event {
GSM_PAGING_SUCCEEDED,
GSM_PAGING_EXPIRED,
GSM_PAGING_BUSY,
};
#endif #endif

View File

@ -12,40 +12,4 @@
struct ran_conn; struct ran_conn;
struct msgb; 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 */ #endif /* _GSM_SUBSCR_H */

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 */ * 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> /* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
@ -31,6 +31,7 @@
struct gsm_network; struct gsm_network;
struct msgb; struct msgb;
struct gsm0808_channel_type;
/* One end of a call */ /* One end of a call */
@ -196,6 +197,15 @@ struct gsm_mncc_bridge {
uint32_t callref[2]; 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); const char *get_mncc_name(int value);
void mncc_set_cause(struct gsm_mncc *data, int loc, int val); void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg); 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_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 #endif

View File

@ -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);

215
include/osmocom/msc/msc_a.h Normal file
View File

@ -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);

View File

@ -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);

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm0808.h>
struct msgb; struct msgb;
struct gsm_network; struct gsm_network;
struct vlr_subscr; struct vlr_subscr;
@ -7,17 +10,51 @@ struct vlr_subscr;
#define MSC_HLR_REMOTE_IP_DEFAULT "127.0.0.1" #define MSC_HLR_REMOTE_IP_DEFAULT "127.0.0.1"
#define MSC_HLR_REMOTE_PORT_DEFAULT OSMO_GSUP_PORT #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 { enum nsap_addr_enc {
NSAP_ADDR_ENC_X213, NSAP_ADDR_ENC_X213,
NSAP_ADDR_ENC_V4RAW, 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 *); 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); 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); 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_alloc(struct gsm_network *net);
int msc_vlr_start(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();

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -3,238 +3,31 @@
#include <stdint.h> #include <stdint.h>
#include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/core/linuxlist.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/gsm/gsm_utils.h>
#define LOG_RAN_CONN(conn, level, fmt, args ...) \ struct ran_peer;
LOG_RAN_CONN_CAT(conn, (conn) ? (conn)->log_subsys : DMSC, level, fmt, ## args) struct osmo_fsm_inst;
struct msgb;
#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 */
};
/* active radio connection of a mobile subscriber */ /* active radio connection of a mobile subscriber */
struct ran_conn { struct ran_conn {
/* global linked list of ran_conn instances */ /* Entry in sccp_ran_inst->ran_conns */
struct llist_head entry; struct llist_head entry;
/* FSM instance to control the RAN connection's permissions and lifetime. */ struct ran_peer *ran_peer;
struct osmo_fsm_inst *fi; uint32_t sccp_conn_id;
enum complete_layer3_type complete_layer3_type;
/* usage count. If this drops to zero, we start the release /* MSC role that this RAN connection belongs to. This will be either an msc_i (currently active
* towards A/Iu */ * connection) or an msc_t (transitory new connection during Handover). */
uint32_t use_count; struct osmo_fsm_inst *msc_role;
uint32_t use_tokens;
/* The MS has opened the conn with a CM Service Request, and we shall bool closing;
* 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;
/* back pointers */
struct gsm_network *network;
/* 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); 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);
void ran_conn_update_id_from_mi(struct ran_conn *conn, const uint8_t *mi, uint8_t mi_len); const char *ran_conn_name(struct ran_conn *conn);
void ran_conn_update_id(struct ran_conn *conn); int ran_conn_down_l2_co(struct ran_conn *conn, struct msgb *l3, bool initial);
const char *ran_conn_get_conn_id(struct ran_conn *conn); void ran_conn_msc_role_gone(struct ran_conn *conn, struct osmo_fsm_inst *msc_role);
void ran_conn_update_id_for_vsub(struct vlr_subscr *for_vsub); void ran_conn_close(struct ran_conn *conn);
void ran_conn_discard(struct ran_conn *conn);
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,
};
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);

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -24,8 +24,11 @@
#include <osmocom/gsm/protocol/gsm_29_118.h> #include <osmocom/gsm/protocol/gsm_29_118.h>
#include <osmocom/msc/vlr.h> #include <osmocom/msc/vlr.h>
#include <osmocom/msc/vlr_sgs.h> #include <osmocom/msc/vlr_sgs.h>
#include <osmocom/msc/paging.h>
#include <osmocom/core/socket.h> #include <osmocom/core/socket.h>
struct msc_a;
static const unsigned int sgs_state_timer_defaults[_NUM_SGS_STATE_TIMERS] = { static const unsigned int sgs_state_timer_defaults[_NUM_SGS_STATE_TIMERS] = {
[SGS_STATE_TS5] = SGS_TS5_DEFAULT, [SGS_STATE_TS5] = SGS_TS5_DEFAULT,
[SGS_STATE_TS6_2] = SGS_TS6_2_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); struct sgs_state *sgs_iface_init(void *ctx, struct gsm_network *network);
int sgs_iface_rx(struct sgs_connection *sgc, struct msgb *msg); 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_paging(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind);
int sgs_iface_tx_dtap_ud(struct msgb *msg); int sgs_iface_tx_dtap_ud(struct msc_a *msc_a, struct msgb *msg);
void sgs_iface_tx_release(struct ran_conn *conn); void sgs_iface_tx_release(struct vlr_subscr *vsub);

View File

@ -28,6 +28,9 @@
#include <osmocom/core/signal.h> #include <osmocom/core/signal.h>
struct msc_a;
struct vty;
/* /*
* Signalling subsystems * Signalling subsystems
*/ */
@ -63,7 +66,7 @@ enum signal_subscr {
/* SS_SCALL signals */ /* SS_SCALL signals */
enum signal_scall { enum signal_scall {
S_SCALL_SUCCESS, S_SCALL_SUCCESS,
S_SCALL_EXPIRED, S_SCALL_FAILED,
S_SCALL_DETACHED, S_SCALL_DETACHED,
}; };
@ -78,23 +81,18 @@ enum signal_global {
struct paging_signal_data { struct paging_signal_data {
struct vlr_subscr *vsub; struct vlr_subscr *vsub;
struct gsm_bts *bts; struct msc_a *msc_a;
int paging_result;
/* NULL in case the paging didn't work */
struct ran_conn *conn;
}; };
struct scall_signal_data { struct scall_signal_data {
struct ran_conn *conn; struct msc_a *msc_a;
void *data; struct vty *vty;
}; };
struct sms_signal_data { struct sms_signal_data {
/* The transaction where this occured */ /* The transaction where this occured */
struct gsm_trans *trans; struct gsm_trans *trans;
/* Can be NULL for SMMA */ /* Can be NULL for SMMA */
struct gsm_sms *sms; struct gsm_sms *sms;
/* int paging result. Only the ones with > 0 */ /* true when paging was successful */
int paging_result; bool paging_result;
}; };

View File

@ -1,15 +1,18 @@
#ifndef _SILENT_CALL_H #ifndef _SILENT_CALL_H
#define _SILENT_CALL_H #define _SILENT_CALL_H
struct ran_conn;
struct gsm0808_channel_type; 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); extern int gsm_silent_call_stop(struct vlr_subscr *vsub);
void trans_silent_call_free(struct gsm_trans *trans);
#if 0 #if 0
extern int silent_call_rx(struct ran_conn *conn, struct msgb *msg); extern int silent_call_rx(struct ran_conn *conn, struct msgb *msg);
extern int silent_call_reroute(struct ran_conn *conn, struct msgb *msg); extern int silent_call_reroute(struct ran_conn *conn, struct msgb *msg);

View File

@ -6,6 +6,7 @@ struct gsm_sms_queue;
struct vty; struct vty;
#define VSUB_USE_SMS_PENDING "SMS-pending" #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_start(struct gsm_network *, int in_flight);
int sms_queue_trigger(struct gsm_sms_queue *); int sms_queue_trigger(struct gsm_sms_queue *);

View File

@ -6,18 +6,21 @@
#include <osmocom/core/fsm.h> #include <osmocom/core/fsm.h>
#include <osmocom/msc/gsm_04_11.h> #include <osmocom/msc/gsm_04_11.h>
#include <osmocom/msc/mncc.h> #include <osmocom/msc/mncc.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/debug.h> #include <osmocom/msc/debug.h>
#include <osmocom/gsm/gsm0411_smc.h> #include <osmocom/gsm/gsm0411_smc.h>
#include <osmocom/gsm/gsm0411_smr.h> #include <osmocom/gsm/gsm0411_smr.h>
struct vty;
/* Used for late TID assignment */ /* Used for late TID assignment */
#define TRANS_ID_UNASSIGNED 0xff #define TRANS_ID_UNASSIGNED 0xff
#define LOG_TRANS_CAT(trans, subsys, level, fmt, args...) \ #define LOG_TRANS_CAT(trans, subsys, level, fmt, args...) \
LOGP(subsys, level, \ LOGP(subsys, level, \
"trans(%s %s callref-0x%x tid-%u%s) " fmt, \ "trans(%s %s callref-0x%x tid-%u%s) " fmt, \
(trans) ? gsm48_pdisc_name((trans)->protocol) : "NULL", \ (trans) ? trans_type_name((trans)->type) : "NULL", \
(trans) ? ((trans)->conn ? (trans)->conn->fi->id : vlr_subscr_name((trans)->vsub)) : "NULL", \ (trans) ? ((trans)->msc_a ? (trans)->msc_a->c.fi->id : vlr_subscr_name((trans)->vsub)) : "NULL", \
(trans) ? (trans)->callref : 0, \ (trans) ? (trans)->callref : 0, \
(trans) ? (trans)->transaction_id : 0, \ (trans) ? (trans)->transaction_id : 0, \
(trans) && (trans)->paging_request ? ",PAGING" : "", \ (trans) && (trans)->paging_request ? ",PAGING" : "", \
@ -34,6 +37,19 @@ enum bridge_state {
BRIDGE_STATE_BRIDGE_ESTABLISHED, 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 */ /* One transaction */
struct gsm_trans { struct gsm_trans {
/* Entry in list of all transactions */ /* Entry in list of all transactions */
@ -42,8 +58,8 @@ struct gsm_trans {
/* Back pointer to the network struct */ /* Back pointer to the network struct */
struct gsm_network *net; struct gsm_network *net;
/* The protocol within which we live */ /* What kind of transaction */
uint8_t protocol; enum trans_type type;
/* The current transaction ID */ /* The current transaction ID */
uint8_t transaction_id; uint8_t transaction_id;
@ -55,7 +71,7 @@ struct gsm_trans {
struct vlr_subscr *vsub; struct vlr_subscr *vsub;
/* The associated connection we are using to transmit messages */ /* The associated connection we are using to transmit messages */
struct ran_conn *conn; struct msc_a *msc_a;
/* reference from MNCC or other application */ /* reference from MNCC or other application */
uint32_t callref; uint32_t callref;
@ -64,7 +80,7 @@ struct gsm_trans {
int tch_recv; int tch_recv;
/* is thats one paging? */ /* is thats one paging? */
struct subscr_request *paging_request; struct paging_request *paging_request;
/* bearer capabilities (rate and codec) */ /* bearer capabilities (rate and codec) */
struct gsm_mncc_bearer_cap bearer_cap; struct gsm_mncc_bearer_cap bearer_cap;
@ -85,7 +101,6 @@ struct gsm_trans {
struct osmo_timer_list timer; struct osmo_timer_list timer;
struct osmo_timer_list timer_guard; struct osmo_timer_list timer_guard;
struct gsm_mncc msg; /* stores setup/disconnect/release message */ struct gsm_mncc msg; /* stores setup/disconnect/release message */
bool assignment_started;
} cc; } cc;
struct { struct {
struct gsm411_smc_inst smc_inst; struct gsm411_smc_inst smc_inst;
@ -105,6 +120,11 @@ struct gsm_trans {
/* Inactivity timer, triggers transaction release */ /* Inactivity timer, triggers transaction release */
struct osmo_timer_list timer_guard; struct osmo_timer_list timer_guard;
} ss; } ss;
struct {
struct gsm0808_channel_type ct;
struct osmo_sockaddr_str rtp_cn;
struct vty *from_vty;
} silent_call;
}; };
struct { struct {
@ -115,8 +135,9 @@ struct gsm_trans {
struct gsm_trans *trans_find_by_id(const struct ran_conn *conn, struct gsm_trans *trans_find_by_type(const struct msc_a *msc_a, enum trans_type type);
uint8_t proto, uint8_t trans_id); struct gsm_trans *trans_find_by_id(const struct msc_a *msc_a,
enum trans_type type, uint8_t trans_id);
struct gsm_trans *trans_find_by_callref(const struct gsm_network *net, struct gsm_trans *trans_find_by_callref(const struct gsm_network *net,
uint32_t callref); uint32_t callref);
struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net, 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 gsm_trans *trans_alloc(struct gsm_network *net,
struct vlr_subscr *vsub, struct vlr_subscr *vsub,
uint8_t protocol, uint8_t trans_id, enum trans_type type, uint8_t trans_id,
uint32_t callref); uint32_t callref);
void trans_free(struct gsm_trans *trans); void trans_free(struct gsm_trans *trans);
int trans_assign_trans_id(const struct gsm_network *net, const struct vlr_subscr *vsub, int trans_assign_trans_id(const struct gsm_network *net, const struct vlr_subscr *vsub,
uint8_t protocol); enum trans_type type);
struct gsm_trans *trans_has_conn(const struct ran_conn *conn); struct gsm_trans *trans_has_conn(const struct msc_a *msc_a);
void trans_conn_closed(const struct ran_conn *conn); void trans_conn_closed(const struct msc_a *msc_a);
static inline int trans_log_subsys(const struct gsm_trans *trans) static inline int trans_log_subsys(const struct gsm_trans *trans)
{ {
if (!trans) if (!trans)
return DMSC; return DMSC;
switch (trans->protocol) { switch (trans->type) {
case GSM48_PDISC_CC: case TRANS_CC:
return DCC; return DCC;
case GSM48_PDISC_SMS: case TRANS_SMS:
return DLSMS; return DLSMS;
default: default:
break; break;
} }
if (trans->msc_a)
return trans->msc_a->c.ran->log_subsys;
return DMSC; return DMSC;
} }

View File

@ -91,11 +91,6 @@ struct vlr_auth_tuple {
#define VLR_KEY_SEQ_INVAL 7 /* GSM 04.08 - 10.5.1.2 */ #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 { enum vlr_subscr_security_context {
VLR_SEC_CTX_NONE, VLR_SEC_CTX_NONE,
VLR_SEC_CTX_GSM, VLR_SEC_CTX_GSM,
@ -162,7 +157,8 @@ struct vlr_subscr {
bool la_allowed; bool la_allowed;
struct osmo_use_count use_count; 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 *lu_fsm;
struct osmo_fsm_inst *auth_fsm; struct osmo_fsm_inst *auth_fsm;
@ -200,20 +196,19 @@ struct vlr_subscr {
struct osmo_timer_list Ts5; struct osmo_timer_list Ts5;
} sgs; } sgs;
struct gsm_classmark classmark; struct osmo_gsm48_classmark classmark;
}; };
enum vlr_ciph { enum vlr_ciph {
VLR_CIPH_NONE, /*< A5/0, no encryption */ VLR_CIPH_NONE = 0, /*< A5/0, no encryption */
VLR_CIPH_A5_1, /*< A5/1, encryption */ VLR_CIPH_A5_1 = 1, /*< A5/1, encryption */
VLR_CIPH_A5_2, /*< A5/2, deprecated export-grade encryption */ VLR_CIPH_A5_2 = 2, /*< A5/2, deprecated export-grade encryption */
VLR_CIPH_A5_3, /*< A5/3, 'new secure' 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) static inline uint8_t vlr_ciph_to_gsm0808_alg_id(enum vlr_ciph ciph)
{ {
switch (ciph) { switch (ciph) {
default:
case VLR_CIPH_NONE: case VLR_CIPH_NONE:
return GSM0808_ALG_ID_A5_0; return GSM0808_ALG_ID_A5_0;
case VLR_CIPH_A5_1: 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; return GSM0808_ALG_ID_A5_2;
case VLR_CIPH_A5_3: case VLR_CIPH_A5_3:
return GSM0808_ALG_ID_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_acc)(void *msc_conn_ref, uint32_t send_tmsi);
int (*tx_lu_rej)(void *msc_conn_ref, enum gsm48_reject_value cause); 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_acc)(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type);
int (*tx_cm_serv_rej)(void *msc_conn_ref, enum gsm48_reject_value cause); 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); 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_common_id)(void *msc_conn_ref);
int (*tx_mm_info)(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 /* notify MSC/SGSN that the given subscriber has been associated
* with this msc_conn_ref */ * with this msc_conn_ref */
int (*subscr_assoc)(void *msc_conn_ref, struct vlr_subscr *vsub); 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 { enum vlr_timer {
@ -271,7 +265,7 @@ enum vlr_timer {
struct vlr_instance { struct vlr_instance {
struct llist_head subscribers; struct llist_head subscribers;
struct llist_head operations; struct llist_head operations;
struct osmo_gsup_client *gsup_client; struct gsup_client_mux *gcm;
struct vlr_ops ops; struct vlr_ops ops;
struct osmo_timer_list lu_expire_timer; struct osmo_timer_list lu_expire_timer;
struct { 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); 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_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)); 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_tmsi_reall_compl(struct vlr_subscr *vsub);
int vlr_subscr_rx_imsi_detach(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); struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops);
int vlr_start(struct ipaccess_unit *ipa_dev, struct vlr_instance *vlr, int vlr_start(struct vlr_instance *vlr, struct gsup_client_mux *gcm);
const char *gsup_server_addr_str, uint16_t gsup_server_port); int vlr_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg);
/* internal use only */ /* 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_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); const char *vlr_subscr_msisdn_or_name(const struct vlr_subscr *vsub);
#define vlr_subscr_find_by_imsi(vlr, imsi, USE) \ #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, uint32_t parent_event_failure,
void *parent_event_data, void *parent_event_data,
struct vlr_instance *vlr, void *msc_conn_ref, 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, const struct osmo_location_area_id *lai,
bool authentication_required, bool authentication_required,
bool ciphering_required, bool ciphering_required,

View File

@ -27,7 +27,7 @@ struct vlr_subscr;
struct vlr_instance; struct vlr_instance;
#define VSUB_USE_SGS "SGs" #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 */ /* See also 3GPP TS 29.118, chapter 4.2.2 States at the VLR */
enum sgs_ue_fsm_state { enum sgs_ue_fsm_state {

View File

@ -28,11 +28,12 @@ noinst_LIBRARIES = \
$(NULL) $(NULL)
libmsc_a_SOURCES = \ libmsc_a_SOURCES = \
a_iface.c \ call_leg.c \
a_iface_bssap.c \ cell_id_list.c \
a_reset.c \ sccp_ran.c \
msc_vty.c \ msc_vty.c \
db.c \ db.c \
e_link.c \
gsm_04_08.c \ gsm_04_08.c \
gsm_04_08_cc.c \ gsm_04_08_cc.c \
gsm_04_11.c \ gsm_04_11.c \
@ -40,31 +41,42 @@ libmsc_a_SOURCES = \
gsm_04_14.c \ gsm_04_14.c \
gsm_04_80.c \ gsm_04_80.c \
gsm_09_11.c \ gsm_09_11.c \
gsm_subscriber.c \ gsup_client_mux.c \
mncc.c \ mncc.c \
mncc_builtin.c \ mncc_builtin.c \
mncc_sock.c \ mncc_sock.c \
msc_ifaces.c \ mncc_call.c \
msc_mgcp.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_conn.c \
ran_infra.c \
ran_msg.c \
ran_msg_a.c \
ran_peer.c \
rrlp.c \ rrlp.c \
rtp_stream.c \
silent_call.c \ silent_call.c \
sms_queue.c \ sms_queue.c \
transaction.c \ transaction.c \
osmo_msc.c \ msc_net_init.c \
ctrl_commands.c \ ctrl_commands.c \
sgs_iface.c \ sgs_iface.c \
sgs_server.c \ sgs_server.c \
sgs_vty.c \ sgs_vty.c \
$(NULL) $(NULL)
if BUILD_IU if BUILD_IU
libmsc_a_SOURCES += \ libmsc_a_SOURCES += \
iucs.c \ ran_msg_iu.c \
iucs_ranap.c \
$(NULL)
else
libmsc_a_SOURCES += \
iu_dummy.c \
$(NULL) $(NULL)
endif endif

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

348
src/libmsc/call_leg.c Normal file
View File

@ -0,0 +1,348 @@
/* Implementation to manage two RTP streams that make up an MO or MT call leg's RTP forwarding. */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/fsm.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/call_leg.h>
#include <osmocom/msc/rtp_stream.h>
#define LOG_CALL_LEG(cl, level, fmt, args...) \
LOGPFSML(cl ? cl->fi : NULL, level, fmt, ##args)
static struct gsm_network *gsmnet = NULL;
enum call_leg_state {
CALL_LEG_ST_ESTABLISHING,
CALL_LEG_ST_ESTABLISHED,
CALL_LEG_ST_RELEASING,
};
struct osmo_tdef g_mgw_tdefs[] = {
{ .T=-1, .default_val=4, .desc="MGCP response timeout" },
{ .T=-2, .default_val=30, .desc="RTP stream establishing timeout" },
{}
};
static const struct osmo_tdef_state_timeout call_leg_fsm_timeouts[32] = {
[CALL_LEG_ST_ESTABLISHING] = { .T = -2 },
[CALL_LEG_ST_RELEASING] = { .T = -2 },
};
#define call_leg_state_chg(cl, state) \
osmo_tdef_fsm_inst_state_chg((cl)->fi, state, call_leg_fsm_timeouts, g_mgw_tdefs, 10)
static struct osmo_fsm call_leg_fsm;
void call_leg_init(struct gsm_network *net)
{
gsmnet = net;
OSMO_ASSERT( osmo_fsm_register(&call_leg_fsm) == 0 );
}
struct call_leg *call_leg_alloc(struct osmo_fsm_inst *parent_fi,
uint32_t parent_event_term,
uint32_t parent_event_rtp_addr_available,
uint32_t parent_event_rtp_complete,
uint32_t parent_event_rtp_released)
{
struct call_leg *cl;
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&call_leg_fsm, parent_fi, parent_event_term);
OSMO_ASSERT(fi);
cl = talloc(fi, struct call_leg);
OSMO_ASSERT(cl);
fi->priv = cl;
*cl = (struct call_leg){
.fi = fi,
.parent_event_rtp_addr_available = parent_event_rtp_addr_available,
.parent_event_rtp_complete = parent_event_rtp_complete,
.parent_event_rtp_released = parent_event_rtp_released,
};
return cl;
}
void call_leg_reparent(struct call_leg *cl,
struct osmo_fsm_inst *new_parent_fi,
uint32_t parent_event_term,
uint32_t parent_event_rtp_addr_available,
uint32_t parent_event_rtp_complete,
uint32_t parent_event_rtp_released)
{
LOG_CALL_LEG(cl, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n",
cl->fi->proc.parent->name, new_parent_fi->name);
osmo_fsm_inst_change_parent(cl->fi, new_parent_fi, parent_event_term);
talloc_steal(new_parent_fi, cl->fi);
cl->parent_event_rtp_addr_available = parent_event_rtp_addr_available;
cl->parent_event_rtp_complete = parent_event_rtp_complete;
cl->parent_event_rtp_released = parent_event_rtp_released;
}
static int call_leg_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct call_leg *cl = fi->priv;
call_leg_release(cl);
return 0;
}
void call_leg_release(struct call_leg *cl)
{
if (!cl)
return;
if (cl->fi->state == CALL_LEG_ST_RELEASING)
return;
call_leg_state_chg(cl, CALL_LEG_ST_RELEASING);
}
static void call_leg_mgw_endpoint_gone(struct call_leg *cl)
{
int i;
cl->mgw_endpoint = NULL;
for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
if (!cl->rtp[i])
continue;
cl->rtp[i]->ci = NULL;
}
}
static void call_leg_fsm_establishing_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct call_leg *cl = fi->priv;
struct rtp_stream *rtps;
int i;
bool established;
switch (event) {
case CALL_LEG_EV_RTP_STREAM_ESTABLISHED:
/* An rtp_stream says it is established. If all are now established, change to state
* CALL_LEG_ST_ESTABLISHED. */
established = true;
for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
if (!rtp_stream_is_established(cl->rtp[i])) {
established = false;
break;
}
}
if (!established)
break;
call_leg_state_chg(cl, CALL_LEG_ST_ESTABLISHED);
break;
case CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE:
rtps = data;
osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_addr_available, rtps);
break;
case CALL_LEG_EV_RTP_STREAM_GONE:
call_leg_release(cl);
break;
case CALL_LEG_EV_MGW_ENDPOINT_GONE:
call_leg_mgw_endpoint_gone(cl);
call_leg_release(cl);
break;
default:
OSMO_ASSERT(false);
}
}
void call_leg_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct call_leg *cl = fi->priv;
osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_complete, cl);
}
void call_leg_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
static void call_leg_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct call_leg *cl = fi->priv;
switch (event) {
case CALL_LEG_EV_RTP_STREAM_GONE:
/* We're already terminating, child RTP streams will also terminate, there is nothing left to do. */
break;
case CALL_LEG_EV_MGW_ENDPOINT_GONE:
call_leg_mgw_endpoint_gone(cl);
break;
default:
OSMO_ASSERT(false);
}
}
static const struct value_string call_leg_fsm_event_names[] = {
OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE),
OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ESTABLISHED),
OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_GONE),
OSMO_VALUE_STRING(CALL_LEG_EV_MGW_ENDPOINT_GONE),
{}
};
#define S(x) (1 << (x))
static const struct osmo_fsm_state call_leg_fsm_states[] = {
[CALL_LEG_ST_ESTABLISHING] = {
.name = "ESTABLISHING",
.in_event_mask = 0
| S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
| S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
| S(CALL_LEG_EV_RTP_STREAM_GONE)
| S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
,
.out_state_mask = 0
| S(CALL_LEG_ST_ESTABLISHED)
| S(CALL_LEG_ST_RELEASING)
,
.action = call_leg_fsm_establishing_established,
},
[CALL_LEG_ST_ESTABLISHED] = {
.name = "ESTABLISHED",
.in_event_mask = 0
| S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
| S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
| S(CALL_LEG_EV_RTP_STREAM_GONE)
| S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
,
.out_state_mask = 0
| S(CALL_LEG_ST_ESTABLISHING)
| S(CALL_LEG_ST_RELEASING)
,
.onenter = call_leg_fsm_established_onenter,
.action = call_leg_fsm_establishing_established, /* same action function as above */
},
[CALL_LEG_ST_RELEASING] = {
.name = "RELEASING",
.in_event_mask = 0
| S(CALL_LEG_EV_RTP_STREAM_GONE)
| S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
,
.onenter = call_leg_fsm_releasing_onenter,
.action = call_leg_fsm_releasing,
},
};
static struct osmo_fsm call_leg_fsm = {
.name = "call_leg",
.states = call_leg_fsm_states,
.num_states = ARRAY_SIZE(call_leg_fsm_states),
.log_subsys = DCC,
.event_names = call_leg_fsm_event_names,
.timer_cb = call_leg_fsm_timer_cb,
};
const struct value_string rtp_direction_names[] = {
OSMO_VALUE_STRING(RTP_TO_RAN),
OSMO_VALUE_STRING(RTP_TO_CN),
{}
};
int call_leg_ensure_rtp_alloc(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans)
{
if (cl->rtp[dir])
return 0;
if (!cl->mgw_endpoint)
cl->mgw_endpoint = osmo_mgcpc_ep_alloc(cl->fi, CALL_LEG_EV_MGW_ENDPOINT_GONE,
gsmnet->mgw.client, gsmnet->mgw.tdefs, cl->fi->id,
"%s", mgcp_client_rtpbridge_wildcard(gsmnet->mgw.client));
if (!cl->mgw_endpoint) {
LOG_CALL_LEG(cl, LOGL_ERROR, "failed to setup MGW endpoint\n");
return -EIO;
}
cl->rtp[dir] = rtp_stream_alloc(cl, dir, call_id, for_trans);
OSMO_ASSERT(cl->rtp[dir]);
return 0;
}
struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direction dir)
{
struct rtp_stream *rtps;
if (!cl)
return NULL;
rtps = cl->rtp[dir];
if (!rtps)
return NULL;
if (!osmo_sockaddr_str_is_set(&rtps->local))
return NULL;
return &rtps->local;
}
/* Make sure an MGW endpoint CI is set up for an RTP connection.
* This is the one-stop for all to either completely set up a new endpoint connection, or to modify an existing one.
* If not yet present, allocate the rtp_stream for the given direction.
* Then, call rtp_stream_set_codec() if codec_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if
* remote_addr_if_known is non-NULL.
* Finally make sure that a CRCX is sent out for this direction, if this has not already happened.
* If the CRCX has already happened but new codec / remote_addr data was passed, call rtp_stream_commit() to trigger an
* MDCX.
*/
int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans,
const enum mgcp_codecs *codec_if_known, const struct osmo_sockaddr_str *remote_addr_if_known)
{
if (call_leg_ensure_rtp_alloc(cl, dir, call_id, for_trans))
return -EIO;
cl->rtp[dir]->crcx_conn_mode = cl->crcx_conn_mode[dir];
if (codec_if_known)
rtp_stream_set_codec(cl->rtp[dir], *codec_if_known);
if (remote_addr_if_known && osmo_sockaddr_str_is_set(remote_addr_if_known))
rtp_stream_set_remote_addr(cl->rtp[dir], remote_addr_if_known);
return rtp_stream_ensure_ci(cl->rtp[dir], cl->mgw_endpoint);
}
int call_leg_local_bridge(struct call_leg *cl1, uint32_t call_id1, struct gsm_trans *trans1,
struct call_leg *cl2, uint32_t call_id2, struct gsm_trans *trans2)
{
enum mgcp_codecs codec;
cl1->local_bridge = cl2;
cl2->local_bridge = cl1;
/* We may just copy the codec info we have for the RAN side of the first leg to the CN side of both legs. This
* also means that if both legs use different codecs the MGW must perform transcoding on the second leg. */
if (!cl1->rtp[RTP_TO_RAN] || !cl1->rtp[RTP_TO_RAN]->codec_known) {
LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side RTP stream codec is not known, not ready for bridging\n");
return -EINVAL;
}
codec = cl1->rtp[RTP_TO_RAN]->codec;
call_leg_ensure_ci(cl1, RTP_TO_CN, call_id1, trans1,
&codec, &cl2->rtp[RTP_TO_CN]->local);
call_leg_ensure_ci(cl2, RTP_TO_CN, call_id2, trans2,
&codec, &cl1->rtp[RTP_TO_CN]->local);
return 0;
}

79
src/libmsc/cell_id_list.c Normal file
View File

@ -0,0 +1,79 @@
/* Manage a list of struct gsm0808_cell_id */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/msc/cell_id_list.h>
int cell_id_list_add_cell(void *talloc_ctx, struct llist_head *list, const struct gsm0808_cell_id *cid)
{
struct cell_id_list_entry *e = cell_id_list_find(list, cid, 0, true);
if (e)
return 0;
e = talloc(talloc_ctx, struct cell_id_list_entry);
OSMO_ASSERT(e);
*e = (struct cell_id_list_entry){
.cell_id = *cid,
};
llist_add_tail(&e->entry, list);
return 1;
}
int cell_id_list_add_list(void *talloc_ctx, struct llist_head *list, const struct gsm0808_cell_id_list2 *cil)
{
struct gsm0808_cell_id one_id;
int i;
int added = 0;
for (i = 0; i < cil->id_list_len; i++) {
one_id = (struct gsm0808_cell_id){
.id_discr = cil->id_discr,
.id = cil->id_list[i],
};
added += cell_id_list_add_cell(talloc_ctx, list, &one_id);
}
return added;
}
void cell_id_list_del_entry(struct cell_id_list_entry *e)
{
llist_del(&e->entry);
talloc_free(e);
}
struct cell_id_list_entry *cell_id_list_find(struct llist_head *list,
const struct gsm0808_cell_id *id,
unsigned int match_nr,
bool exact_match)
{
struct cell_id_list_entry *e;
llist_for_each_entry(e, list, entry) {
if (gsm0808_cell_ids_match(id, &e->cell_id, exact_match)) {
if (match_nr)
match_nr--;
else
return e;
}
}
return NULL;
}

372
src/libmsc/e_link.c Normal file
View File

@ -0,0 +1,372 @@
/* E-interface messaging over a GSUP connection */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/core/fsm.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsup_client_mux.h>
#include <osmocom/msc/e_link.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_roles.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/ran_infra.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/msc_a_remote.h>
#include <osmocom/msc/msc_i.h>
#include <osmocom/msc/msc_i_remote.h>
#include <osmocom/msc/msc_t.h>
#include <osmocom/msc/msc_t_remote.h>
#define LOG_E_LINK(e_link, level, fmt, args...) \
LOGPFSML(e_link->msc_role, level, fmt, ##args)
#define LOG_E_LINK_CAT(e_link, ss, level, fmt, args...) \
LOGPFSMSL(e_link->msc_role, ss, level, fmt, ##args)
void e_link_assign(struct e_link *e, struct osmo_fsm_inst *msc_role)
{
struct msc_role_common *c;
if (e->msc_role) {
c = e->msc_role->priv;
if (c->remote_to == e) {
c->remote_to = NULL;
msub_update_id(c->msub);
}
}
c = msc_role->priv;
e->msc_role = msc_role;
c->remote_to = e;
msub_update_id(c->msub);
LOG_E_LINK(e, LOGL_DEBUG, "Assigned E-link to %s\n", e_link_name(e));
}
struct e_link *e_link_alloc(struct gsup_client_mux *gcm, struct osmo_fsm_inst *msc_role,
const uint8_t *remote_name, size_t remote_name_len)
{
struct e_link *e;
struct msc_role_common *c = msc_role->priv;
/* use msub as talloc parent, so we can move an e_link from msc_t to msc_i when it is established. */
e = talloc_zero(c->msub, struct e_link);
if (!e)
return NULL;
e->gcm = gcm;
/* Expecting all code paths to print the remote name according to remote_name_len. To be paranoid, place a nul
* character after the end. */
e->remote_name = talloc_size(e, remote_name_len + 1);
OSMO_ASSERT(e->remote_name);
memcpy(e->remote_name, remote_name, remote_name_len);
e->remote_name[remote_name_len] = '\0';
e->remote_name_len = remote_name_len;
e_link_assign(e, msc_role);
return e;
}
void e_link_free(struct e_link *e)
{
if (!e)
return;
if (e->msc_role) {
struct msc_role_common *c = e->msc_role->priv;
if (c->remote_to == e)
c->remote_to = NULL;
}
talloc_free(e);
}
/* Set up IMSI, source and destination names in given gsup_msg struct. */
int e_prep_gsup_msg(struct e_link *e, struct osmo_gsup_message *gsup_msg)
{
struct msc_role_common *c;
struct vlr_subscr *vsub;
const char *local_msc_name = NULL;
if (e->gcm && e->gcm->gsup_client && e->gcm->gsup_client->ipa_dev) {
local_msc_name = e->gcm->gsup_client->ipa_dev->serno;
if (!local_msc_name)
local_msc_name = e->gcm->gsup_client->ipa_dev->unit_name;
}
if (!local_msc_name) {
LOG_E_LINK(e, LOGL_ERROR, "Cannot prep E-interface GSUP message: no local MSC name defined\n");
return -ENODEV;
}
c = e->msc_role->priv;
vsub = c->msub->vsub;
*gsup_msg = (struct osmo_gsup_message){
.message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC,
.source_name = (const uint8_t*)local_msc_name,
.source_name_len = strlen(local_msc_name),
.destination_name = (const uint8_t*)e->remote_name,
.destination_name_len = e->remote_name_len,
};
if (vsub)
OSMO_STRLCPY_ARRAY(gsup_msg->imsi, vsub->imsi);
return 0;
}
int e_tx(struct e_link *e, const struct osmo_gsup_message *gsup_msg)
{
LOG_E_LINK_CAT(e, DLGSUP, LOGL_DEBUG, "Tx GSUP %s to %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type),
e_link_name(e));
return gsup_client_mux_tx(e->gcm, gsup_msg);
}
const char *e_link_name(struct e_link *e)
{
return osmo_escape_str((const char*)e->remote_name, e->remote_name_len);
}
static struct msub *msc_new_msc_t_for_handover_request(struct gsm_network *net,
const struct osmo_gsup_message *gsup_msg)
{
struct ran_infra *ran;
struct msub *msub;
struct msc_a *msc_a;
struct vlr_subscr *vsub;
switch (gsup_msg->an_apdu.access_network_proto) {
case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006:
ran = &msc_ran_infra[OSMO_RAT_GERAN_A];
break;
case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413:
ran = &msc_ran_infra[OSMO_RAT_UTRAN_IU];
break;
default:
ran = NULL;
break;
}
if (!ran || !ran->ran_dec_l2) {
LOGP(DLGSUP, LOGL_ERROR, "Cannot handle AN-proto %s\n",
an_proto_name(gsup_msg->an_apdu.access_network_proto));
return NULL;
}
msub = msub_alloc(net);
/* To properly compose GSUP messages going back to the remote peer, make sure the incoming IMSI is set in a
* vlr_subscr associated with the msub. */
vsub = vlr_subscr_find_or_create_by_imsi(net->vlr, gsup_msg->imsi, __func__, NULL);
msub_set_vsub(msub, vsub);
vlr_subscr_put(vsub, __func__);
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n");
msc_a = msc_a_remote_alloc(msub, ran, gsup_msg->source_name, gsup_msg->source_name_len);
if (!msc_a) {
osmo_fsm_inst_term(msub->fi, OSMO_FSM_TERM_REQUEST, NULL);
return NULL;
}
LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n");
return msub;
}
static bool name_matches(const uint8_t *name, size_t len, const uint8_t *match_name, size_t match_len)
{
if (!match_name)
return !name || !len;
if (len != match_len)
return false;
return memcmp(name, match_name, len) == 0;
}
static bool e_link_matches_gsup_msg_source_name(const struct e_link *e, const struct osmo_gsup_message *gsup_msg)
{
return name_matches(gsup_msg->source_name, gsup_msg->source_name_len, e->remote_name, e->remote_name_len);
}
static int msc_a_i_t_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg)
{
struct gsm_network *net = data;
struct vlr_instance *vlr = net->vlr;
struct vlr_subscr *vsub;
struct msub *msub;
struct osmo_fsm_inst *msc_role = NULL;
struct e_link *e;
struct msc_role_common *c;
int i;
OSMO_ASSERT(net);
vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__);
if (vsub)
LOGP(DLGSUP, LOGL_DEBUG, "Found VLR entry for IMSI %s\n", gsup_msg->imsi);
msub = msub_for_vsub(vsub);
if (msub)
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Found already attached subscriber for IMSI %s\n",
gsup_msg->imsi);
if (vsub) {
vlr_subscr_put(vsub, __func__);
vsub = NULL;
}
/* Only for an incoming Handover Request: create a new remote-MSC-A as proxy for the MSC-A that is sending the
* Handover Request */
if (!msub && gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) {
msub = msc_new_msc_t_for_handover_request(net, gsup_msg);
}
if (!msub) {
LOGP(DLGSUP, LOGL_ERROR, "%s: Cannot find subscriber for IMSI %s\n",
__func__, osmo_quote_str(gsup_msg->imsi, -1));
return -EINVAL;
}
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Rx GSUP %s\n", osmo_gsup_message_type_name(gsup_msg->message_type));
e = NULL;
for (i = 0; i < ARRAY_SIZE(msub->role); i++) {
msc_role = msub->role[i];
if (!msc_role) {
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "No %s\n", msc_role_name(i));
continue;
}
c = msc_role->priv;
if (!c->remote_to) {
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has no remote\n", msc_role_name(i));
continue;
}
if (!e_link_matches_gsup_msg_source_name(c->remote_to, gsup_msg)) {
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has remote to mismatching %s\n", msc_role_name(i),
c->remote_to->remote_name);
continue;
}
/* Found a match. */
e = c->remote_to;
break;
}
if (!e) {
LOG_MSUB_CAT(msub, DLGSUP, LOGL_ERROR,
"There is no E link that matches: Rx GSUP %s from %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type),
osmo_quote_str((const char*)gsup_msg->source_name, gsup_msg->source_name_len));
return -EINVAL;
}
LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG,
"Rx GSUP %s from %s %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type),
msc_role_name(c->role),
e_link_name(e));
return osmo_fsm_inst_dispatch(msc_role, MSC_REMOTE_EV_RX_GSUP, (void*)gsup_msg);
}
void msc_a_i_t_gsup_init(struct gsm_network *net)
{
OSMO_ASSERT(net->gcm);
OSMO_ASSERT(net->vlr);
net->gcm->rx_cb[OSMO_GSUP_MESSAGE_CLASS_INTER_MSC] = (struct gsup_client_mux_rx_cb){
.func = msc_a_i_t_gsup_rx,
.data = net,
};
}
int gsup_msg_assign_an_apdu(struct osmo_gsup_message *gsup_msg, struct an_apdu *an_apdu)
{
if (!an_apdu) {
LOGP(DLGSUP, LOGL_ERROR, "Cannot assign NULL AN-APDU\n");
return -EINVAL;
}
gsup_msg->an_apdu = (struct osmo_gsup_an_apdu){
.access_network_proto = an_apdu->an_proto,
};
if (an_apdu->msg) {
gsup_msg->an_apdu.data = msgb_l2(an_apdu->msg);
gsup_msg->an_apdu.data_len = msgb_l2len(an_apdu->msg);
if (!gsup_msg->an_apdu.data || !gsup_msg->an_apdu.data_len) {
LOGP(DLGSUP, LOGL_ERROR, "Cannot assign AN-APDU without msg->l2 to GSUP message: %s\n",
msgb_hexdump(an_apdu->msg));
return -EINVAL;
}
}
/* We are composing a struct osmo_gsup_msg from the osmo-msc internal struct an_apdu. The an_apdu may contain
* additional info in form of a partly filled an_apdu->e_info. Make sure that data ends up in the resulting full
* osmo_gsup_message. */
if (an_apdu->e_info) {
const struct osmo_gsup_message *s = an_apdu->e_info;
gsup_msg->msisdn_enc = s->msisdn_enc;
gsup_msg->msisdn_enc_len = s->msisdn_enc_len;
if (s->cause_rr_set) {
gsup_msg->cause_rr = s->cause_rr;
gsup_msg->cause_rr_set = true;
}
if (s->cause_bssap_set) {
gsup_msg->cause_bssap = s->cause_bssap;
gsup_msg->cause_bssap_set = true;
}
if (s->cause_sm)
gsup_msg->cause_sm = s->cause_sm;
}
return 0;
}
/* Allocate a new msgb to contain the gsup_msg->an_apdu's data as l2h.
* The msgb will have sufficient headroom to be passed down a RAN peer's SCCP user SAP. */
struct msgb *gsup_msg_to_msgb(const struct osmo_gsup_message *gsup_msg)
{
struct msgb *pdu;
const uint8_t *pdu_data = gsup_msg->an_apdu.data;
uint8_t pdu_len = gsup_msg->an_apdu.data_len;
if (!pdu_data || !pdu_len)
return NULL;
/* Strictly speaking this is not limited to BSSMAP, but why not just use those sizes. */
pdu = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "AN-APDU from gsup_msg");
pdu->l2h = msgb_put(pdu, pdu_len);
memcpy(pdu->l2h, pdu_data, pdu_len);
return pdu;
}
/* Compose a struct an_apdu from the data found in gsup_msg. gsup_msg_to_msgb() is used to wrap the data in a static
* msgb, so the returned an_apdu->msg must be freed if not NULL. */
void gsup_msg_to_an_apdu(struct an_apdu *an_apdu, const struct osmo_gsup_message *gsup_msg)
{
*an_apdu = (struct an_apdu){
.an_proto = gsup_msg->an_apdu.access_network_proto,
.msg = gsup_msg_to_msgb(gsup_msg),
.e_info = gsup_msg,
};
}

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,13 @@
#include <osmocom/abis/e1_input.h> #include <osmocom/abis/e1_input.h>
#include <osmocom/core/bitvec.h> #include <osmocom/core/bitvec.h>
#include <osmocom/msc/vlr.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/gsm48.h>
#include <osmocom/gsm/gsm0480.h> #include <osmocom/gsm/gsm0480.h>
@ -60,17 +66,29 @@
#include <osmocom/core/byteswap.h> #include <osmocom/core/byteswap.h>
#include <osmocom/gsm/tlv.h> #include <osmocom/gsm/tlv.h>
#include <osmocom/crypt/auth.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> #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) 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; data[0] = ss_notify->len - 1;
gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh)); gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh));
gh->msg_type = GSM48_MT_CC_FACILITY; 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 /* 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) static void new_cc_state(struct gsm_trans *trans, int state)
{ {
if (state > 31 || state < 0) 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 = msgb_put(msg, 1);
call_state[0] = 0xc0 | 0x00; 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) 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); 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 */ /* send release to L4, if callref still exists */
if (trans->callref) { 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 */ /* Ressource unavailable */
mncc_release_ind(trans->net, trans, trans->callref, mncc_release_ind(trans->net, trans, trans->callref,
GSM48_CAUSE_LOC_PRN_S_LU, 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); new_cc_state(trans, GSM_CSTATE_NULL);
gsm48_stop_guard_timer(trans); gsm48_stop_guard_timer(trans);
if (trans->msc_a && trans->msc_a->cc.active_trans == trans)
trans->msc_a->cc.active_trans = NULL;
} }
static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
/* call-back from paging the B-end of the connection */ /* call-back from paging the B-end of the connection */
static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event, static void cc_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans)
struct msgb *msg, void *_conn, void *_transt)
{ {
struct ran_conn *conn = _conn; if (trans->msc_a) {
struct gsm_trans *transt = _transt; LOG_MSC_A_CAT(msc_a, DPAG, LOGL_ERROR,
enum gsm_paging_event paging_event = event; "Handle paging error: transaction already associated with subscriber,"
" apparently it was already handled. Skip.\n");
OSMO_ASSERT(!transt->conn); return;
switch (paging_event) {
case GSM_PAGING_SUCCEEDED:
LOG_TRANS(transt, LOGL_DEBUG, "Paging succeeded\n");
OSMO_ASSERT(conn);
/* Assign conn */
transt->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_CC);
transt->paging_request = NULL;
/* 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");
/* Temporarily out of order */
mncc_release_ind(transt->net, transt,
transt->callref,
GSM48_CAUSE_LOC_PRN_S_LU,
GSM48_CC_CAUSE_DEST_OOO);
transt->callref = 0;
transt->paging_request = NULL;
trans_free(transt);
break;
} }
return 0; if (msc_a) {
LOG_TRANS(trans, LOGL_DEBUG, "Paging succeeded\n");
/* Assign conn */
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(trans, &trans->cc.msg);
} else {
LOG_TRANS(trans, LOGL_DEBUG, "Paging expired\n");
/* Temporarily out of order */
mncc_release_ind(trans->net, trans,
trans->callref,
GSM48_CAUSE_LOC_PRN_S_LU,
GSM48_CC_CAUSE_DEST_OOO);
trans->callref = 0;
trans->paging_request = NULL;
trans_free(trans);
}
} }
/* bridge channels of two transactions */ /* 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 *trans1 = trans_find_by_callref(net, bridge->callref[0]);
struct gsm_trans *trans2 = trans_find_by_callref(net, bridge->callref[1]); 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) { if (!trans1 || !trans2) {
LOG_TRANS(trans1 ? : trans2, LOGL_ERROR, "Cannot MNCC_BRIDGE, one or both call legs are unset\n"); LOG_TRANS(trans1 ? : trans2, LOGL_ERROR, "Cannot MNCC_BRIDGE, one or both call legs are unset\n");
return -EIO; 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(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"); LOG_TRANS(trans2, LOGL_ERROR, "Cannot MNCC_BRIDGE, one or both call legs lack an active connection\n");
return -EIO; 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(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); 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? */ /* This call bridging mechanism is only used with the internal MNCC (with external MNCC briding would be done by
log_set_context(LOG_CTX_VLR_SUBSCR, trans1->vsub); * 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;
/* This call briding mechanism is only used with the internal MNCC. return call_leg_local_bridge(cl1, trans1->callref, trans1, cl2, trans2->callref, trans2);
* 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;
return 0;
} }
static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) 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); 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) static void gsm48_cc_timeout(void *arg)
{ {
struct gsm_trans *trans = arg; struct gsm_trans *trans = arg;
@ -452,7 +448,7 @@ static void gsm48_cc_timeout(void *arg)
/* disconnect both calls from the bridge */ /* disconnect both calls from the bridge */
static inline void disconnect_bridge(struct gsm_network *net, 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 *trans0 = trans_find_by_callref(net, bridge->callref[0]);
struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[1]); 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 /* Create a copy of the bearer capability
* in the transaction struct, so we can use * in the transaction struct, so we can use
* this information later */ * this information later */
memcpy(&trans->bearer_cap,&setup.bearer_cap, memcpy(&trans->bearer_cap, &setup.bearer_cap,
sizeof(trans->bearer_cap)); sizeof(trans->bearer_cap));
} }
/* facility */ /* facility */
@ -606,7 +602,7 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
} }
/* Get free transaction_id */ /* 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) { if (trans_id < 0) {
/* no free transaction ID */ /* no free transaction ID */
rc = mncc_release_ind(trans->net, trans, trans->callref, 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]); 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) 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); new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
/* Assign call (if not done yet) */ /* 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 /* don't continue, if there were problems with
* the call assignment. */ * 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) if (proceeding->fields & MNCC_F_PROGRESS)
gsm48_encode_progress(msg, 0, &proceeding->progress); gsm48_encode_progress(msg, 0, &proceeding->progress);
rc = gsm48_conn_sendmsg(msg, trans->conn, trans); rc = trans_tx_gsm48(trans, msg);
if (rc) if (rc)
return rc; return rc;
/* Assign call (if not done yet) */ /* 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) 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); 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) 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) if (progress->fields & MNCC_F_USERUSER)
gsm48_encode_useruser(msg, 0, &progress->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) 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); 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) 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); 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) 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); return mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &disc);
} }
static struct gsm_mncc_cause default_cause = { 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); 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) 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 */ /* release collision 5.4.5 */
rc = mncc_recvmsg(trans->net, trans, MNCC_REL_CNF, &rel); rc = mncc_recvmsg(trans->net, trans, MNCC_REL_CNF, &rel);
} else { } else {
rc = gsm48_tx_simple(trans->conn, rc = gsm48_tx_simple(trans->msc_a,
GSM48_PDISC_CC | (trans->transaction_id << 4), GSM48_PDISC_CC | (trans->transaction_id << 4),
GSM48_MT_CC_RELEASE_COMPL); GSM48_MT_CC_RELEASE_COMPL);
rc = mncc_recvmsg(trans->net, trans, MNCC_REL_IND, &rel); 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) if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
new_cc_state(trans, 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) 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) if (rel->fields & MNCC_F_USERUSER)
gsm48_encode_useruser(msg, 0, &rel->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); trans_free(trans);
@ -1233,7 +1228,7 @@ static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
/* facility */ /* facility */
gsm48_encode_facility(msg, 1, &fac->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) 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; 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) 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 else
gsm48_encode_cause(msg, 1, &default_cause); 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) 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; 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) 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 else
gsm48_encode_cause(msg, 1, &default_cause); 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) 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) if (dtmf->fields & MNCC_F_KEYPAD)
gsm48_encode_keypad(msg, dtmf->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) 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 else
gsm48_encode_cause(msg, 1, &default_cause); 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) 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; 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) 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); 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) 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); 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) 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); 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) 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 */ /* notify */
gsm48_encode_notify(msg, notify->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) 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) if (user->more)
gsm48_encode_more(msg); 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) 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); 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, static int 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, int cmd, struct osmo_sockaddr_str *rtp_addr, uint32_t payload_type,
uint32_t payload_msg_type) uint32_t payload_msg_type)
{ {
uint8_t data[sizeof(struct gsm_mncc)]; uint8_t data[sizeof(struct gsm_mncc)];
struct gsm_mncc_rtp *rtp; 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->callref = callref;
rtp->msg_type = cmd; rtp->msg_type = cmd;
rtp->ip = osmo_htonl(addr); if (rtp_addr) {
rtp->port = port; rtp->ip = osmo_htonl(inet_addr(rtp_addr->ip));
rtp->port = rtp_addr->port;
}
rtp->payload_type = payload_type; rtp->payload_type = payload_type;
rtp->payload_msg_type = payload_msg_type; rtp->payload_msg_type = payload_msg_type;
mncc_recvmsg(net, trans, cmd, (struct gsm_mncc *)data); return 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);
} }
static void mncc_recv_rtp_err(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref, int cmd) 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) 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; return -EIO;
} }
log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub); 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"); LOG_TRANS_CAT(trans, DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
mncc_recv_rtp_err(net, trans, callref, MNCC_RTP_CREATE); mncc_recv_rtp_err(net, trans, callref, MNCC_RTP_CREATE);
return 0; return 0;
@ -1698,7 +1656,7 @@ static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
trans->tch_rtp_create = true; trans->tch_rtp_create = true;
/* Assign call (if not done yet) */ /* 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 */ /* 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 /* This function is called as soon as the port, on which the
* mgcp-gw expects the incoming RTP stream from the remote * mgcp-gw expects the incoming RTP stream from the remote
* end (e.g. Asterisk) is known. */ * 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;
struct ran_conn *conn = trans->conn; /* FIXME This has to be set to some meaningful value.
struct gsm_network *network = conn->network; * 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;
mncc_recv_rtp_sock(network, trans, MNCC_RTP_CREATE); rtp_cn_local = call_leg_local_ip(cl, RTP_TO_CN);
return 0; 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;
}
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 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 /* 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 * 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; return -EIO;
} }
log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub); 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"); 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); 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)); LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(MNCC_RTP_CONNECT));
addr.s_addr = osmo_htonl(rtp->ip); cl = trans->msc_a->cc.call_leg;
return msc_mgcp_call_complete(trans, rtp->port, inet_ntoa(addr)); 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));
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 { static struct downstate {
@ -1809,24 +1800,24 @@ static struct downstate {
(sizeof(downstatelist) / sizeof(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; int i, rc = 0;
struct gsm_trans *trans = NULL, *transt; struct msc_a *msc_a = NULL;
struct ran_conn *conn = NULL; struct gsm_trans *trans = NULL;
struct gsm_mncc *data = arg, rel; const struct gsm_mncc *data;
/* handle special messages */ /* handle special messages */
switch(msg_type) { switch(msg->msg_type) {
case MNCC_BRIDGE: case MNCC_BRIDGE:
rc = tch_bridge(net, arg); rc = tch_bridge(net, &msg->bridge);
if (rc < 0) if (rc < 0)
disconnect_bridge(net, arg, -rc); disconnect_bridge(net, &msg->bridge, -rc);
return rc; return rc;
case MNCC_RTP_CREATE: case MNCC_RTP_CREATE:
return tch_rtp_create(net, data->callref); return tch_rtp_create(net, msg->rtp.callref);
case MNCC_RTP_CONNECT: case MNCC_RTP_CONNECT:
return tch_rtp_connect(net, arg); return tch_rtp_connect(net, &msg->rtp);
case MNCC_RTP_FREE: case MNCC_RTP_FREE:
/* unused right now */ /* unused right now */
return -EIO; 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_TCHH_FRAME:
case GSM_TCH_FRAME_AMR: case GSM_TCH_FRAME_AMR:
LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP streams must be handled externally; %s not supported.\n", 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; return -ENOTSUP;
} }
memset(&rel, 0, sizeof(struct gsm_mncc)); data = &msg->signal;
rel.callref = data->callref;
/* Find callref */ /* Find callref */
trans = trans_find_by_callref(net, data->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) { if (!trans) {
struct vlr_subscr *vsub; 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", 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 */ /* Invalid call reference */
return mncc_release_ind(net, NULL, data->callref, return mncc_release_ind(net, NULL, data->callref,
GSM48_CAUSE_LOC_PRN_S_LU, 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]) { if (!data->called.number[0] && !data->imsi[0]) {
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "Neither number nor IMSI in %s\n", 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 */ /* Invalid number */
return mncc_release_ind(net, NULL, data->callref, return mncc_release_ind(net, NULL, data->callref,
GSM48_CAUSE_LOC_PRN_S_LU, 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__); vsub = vlr_subscr_find_by_msisdn(net->vlr, data->called.number, __func__);
if (!vsub) if (!vsub)
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "rx %s for unknown subscriber number '%s'\n", 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 { } else {
vsub = vlr_subscr_find_by_imsi(net->vlr, data->imsi, __func__); vsub = vlr_subscr_find_by_imsi(net->vlr, data->imsi, __func__);
if (!vsub) if (!vsub)
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "rx %s for unknown subscriber IMSI '%s'\n", 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) if (!vsub)
return mncc_release_ind(net, NULL, data->callref, GSM48_CAUSE_LOC_PRN_S_LU, 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 subscriber is not "attached" */
if (!vsub->lu_complete) { if (!vsub->lu_complete) {
LOG_TRANS_CAT(trans, DCC, LOGL_ERROR, "rx %s for subscriber that is not attached: %s\n", 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__); vlr_subscr_put(vsub, __func__);
/* Temporarily out of order */ /* Temporarily out of order */
return mncc_release_ind(net, NULL, data->callref, 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); GSM48_CC_CAUSE_DEST_OOO);
} }
/* Create transaction */ /* Create transaction */
trans = trans_alloc(net, vsub, GSM48_PDISC_CC, trans = trans_alloc(net, vsub, TRANS_CC,
TRANS_ID_UNASSIGNED, data->callref); TRANS_ID_UNASSIGNED, data->callref);
if (!trans) { if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, "No memory for trans.\n"); 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; return -ENOMEM;
} }
/* Find conn */ /* Find valid conn */
conn = connection_for_subscr(vsub); msc_a = msc_a_for_vsub(vsub, true);
/* If subscriber has no conn */ /* If subscriber has no conn */
if (!conn) { if (!msc_a) {
/* find transaction with this subscriber already paging */
llist_for_each_entry(transt, &net->trans_list, entry) { if (vsub->cs.is_paging) {
/* Transaction of our conn? */
if (transt == trans ||
transt->vsub != vsub)
continue;
LOG_TRANS(trans, LOGL_DEBUG, LOG_TRANS(trans, LOGL_DEBUG,
"rx %s, subscriber not yet connected, paging already started\n", "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__); vlr_subscr_put(vsub, __func__);
trans_free(trans); trans_free(trans);
return 0; 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)); memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
/* Request a channel */ /* Request a channel */
trans->paging_request = subscr_request_conn( trans->paging_request = paging_request_start(vsub, PAGING_CAUSE_CALL_CONVERSATIONAL,
vsub, cc_paging_cb, trans, "MNCC: establish call");
setup_trig_pag_evt,
trans,
"MNCC: establish call",
SGSAP_SERV_IND_CS_CALL);
if (!trans->paging_request) { if (!trans->paging_request) {
LOG_TRANS(trans, LOGL_ERROR, "Failed to allocate paging token.\n"); LOG_TRANS(trans, LOGL_ERROR, "Failed to allocate paging token.\n");
vlr_subscr_put(vsub, __func__);
trans_free(trans); trans_free(trans);
return 0;
} }
vlr_subscr_put(vsub, __func__); vlr_subscr_put(vsub, __func__);
return 0; return 0;
} }
/* Assign conn */ /* 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 */ trans->dlci = 0x00; /* SAPI=0, not SACCH */
vlr_subscr_put(vsub, __func__); vlr_subscr_put(vsub, __func__);
} else { } 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_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); gsm48_start_guard_timer(trans);
if (trans->conn) if (trans->msc_a)
conn = trans->conn; msc_a = trans->msc_a;
/* if paging did not respond yet */ /* if paging did not respond yet */
if (!conn) { if (!msc_a) {
LOG_TRANS(trans, LOGL_DEBUG, "rx %s in paging state\n", get_mncc_name(msg_type)); 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, mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
GSM48_CC_CAUSE_NORM_CALL_CLEAR); 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); rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
else else
rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel); 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; return rc;
} else { } else {
LOG_TRANS(trans, LOGL_DEBUG, "rx %s in state %s\n", 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 */ /* Find function for current state and message */
for (i = 0; i < DOWNSLLEN; i++) 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)) && ((1 << trans->cc.state) & downstatelist[i].states))
break; break;
if (i == DOWNSLLEN) { if (i == DOWNSLLEN) {
LOG_TRANS(trans, LOGL_DEBUG, "Message '%s' unhandled at state '%s'\n", 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; return 0;
} }
rc = downstatelist[i].rout(trans, arg); rc = downstatelist[i].rout(trans, (void*)msg);
return rc; 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 { static struct datastate {
uint32_t states; uint32_t states;
@ -2052,12 +2094,14 @@ static struct datastate {
#define DATASLLEN \ #define DATASLLEN \
(sizeof(datastatelist) / sizeof(struct datastate)) (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); struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t msg_type = gsm48_hdr_msg_type(gh); uint8_t msg_type = gsm48_hdr_msg_type(gh);
uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh); uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
struct gsm_trans *trans = NULL; 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; int i, rc = 0;
if (msg_type & 0x80) { if (msg_type & 0x80) {
@ -2065,33 +2109,44 @@ int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg)
return -EINVAL; return -EINVAL;
} }
if (!conn->vsub) { if (!vsub) {
LOG_TRANS(trans, LOGL_ERROR, "Invalid conn: no subscriber\n"); LOG_TRANS(trans, LOGL_ERROR, "Invalid conn: no subscriber\n");
return -EINVAL; return -EINVAL;
} }
/* Find transaction */ /* 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 */ /* Create transaction */
if (!trans) { if (!trans) {
DEBUGP(DCC, "Unknown transaction ID %x, "
"creating new trans.\n", transaction_id);
/* Create transaction */ /* Create transaction */
trans = trans_alloc(conn->network, conn->vsub, trans = trans_alloc(net, vsub,
GSM48_PDISC_CC, TRANS_CC,
transaction_id, new_callref++); transaction_id, msc_cc_next_outgoing_callref());
if (!trans) { if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, "No memory for trans.\n"); 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_PDISC_CC | (transaction_id << 4),
GSM48_MT_CC_RELEASE_COMPL); GSM48_MT_CC_RELEASE_COMPL);
return -ENOMEM; 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 */ /* 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 */ 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), 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; break;
if (i == DATASLLEN) { if (i == DATASLLEN) {
LOG_TRANS(trans, LOGL_ERROR, "Message unhandled at this state.\n"); 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; return 0;
} }
@ -2111,6 +2174,5 @@ int gsm0408_rcv_cc(struct ran_conn *conn, struct msgb *msg)
rc = datastatelist[i].rout(trans, msg); rc = datastatelist[i].rout(trans, msg);
ran_conn_communicating(conn);
return rc; return rc;
} }

View File

@ -51,8 +51,10 @@
#include <osmocom/msc/signal.h> #include <osmocom/msc/signal.h>
#include <osmocom/msc/db.h> #include <osmocom/msc/db.h>
#include <osmocom/msc/transaction.h> #include <osmocom/msc/transaction.h>
#include <osmocom/msc/msc_ifaces.h>
#include <osmocom/msc/vlr.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 #ifdef BUILD_SMPP
#include "smpp_smsc.h" #include "smpp_smsc.h"
@ -61,7 +63,6 @@
void *tall_gsms_ctx; void *tall_gsms_ctx;
static uint32_t new_callref = 0x40000001; static uint32_t new_callref = 0x40000001;
struct gsm_sms *sms_alloc(void) struct gsm_sms *sms_alloc(void)
{ {
return talloc_zero(tall_gsms_ctx, struct gsm_sms); 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)); LOG_TRANS(trans, LOGL_DEBUG, "GSM4.11 TX %s\n", msgb_hexdump(msg));
msg->l3h = msg->data; 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) */ /* Paging callback for MT SMS (Paging is triggered by SMC) */
static int paging_cb_mmsms_est_req(unsigned int hooknum, unsigned int event, static void mmsms_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans)
struct msgb *msg, void *_conn, void *_trans)
{ {
struct ran_conn *conn = _conn;
struct gsm_trans *trans = _trans;
struct gsm_sms *sms = trans->sms.sms; 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"); LOG_TRANS(trans, LOGL_DEBUG, "%s(%s)\n", __func__, msc_a ? "success" : "expired");
if (hooknum != GSM_HOOK_RR_PAGING) if (msc_a) {
return -EINVAL; /* Paging succeeded */
/* Paging procedure has finished */
trans->paging_request = NULL;
switch (event) {
case GSM_PAGING_SUCCEEDED:
/* Associate transaction with established connection */ /* 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 */ /* Confirm successful connection establishment */
gsm411_smc_recv(&trans->sms.smc_inst, gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_EST_CNF, NULL, 0);
GSM411_MMSMS_EST_CNF, NULL, 0); } else {
break; /* Paging expired or failed */
case GSM_PAGING_EXPIRED:
case GSM_PAGING_BUSY:
/* Inform SMC about channel establishment failure */ /* Inform SMC about channel establishment failure */
gsm411_smc_recv(&trans->sms.smc_inst, gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_REL_IND, NULL, 0);
GSM411_MMSMS_REL_IND, NULL, 0);
/* gsm411_send_rp_data() doesn't set trans->sms.sms */ /* gsm411_send_rp_data() doesn't set trans->sms.sms */
if (sms != NULL) { if (sms != NULL) {
/* Notify the SMSqueue and free stored SMS */ /* 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; trans->sms.sms = NULL;
sms_free(sms); sms_free(sms);
} }
/* Destroy this transaction */ /* Destroy this transaction */
trans_free(trans); 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) 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); OSMO_ASSERT(trans->vsub != NULL);
/* Check if connection is already established */ /* 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"); LOG_TRANS(trans, LOGL_DEBUG, "Using an existing connection\n");
return gsm411_smc_recv(&trans->sms.smc_inst, return gsm411_smc_recv(&trans->sms.smc_inst,
GSM411_MMSMS_EST_CNF, NULL, 0); GSM411_MMSMS_EST_CNF, NULL, 0);
@ -191,15 +174,12 @@ static int gsm411_mmsms_est_req(struct gsm_trans *trans)
/* Initiate Paging procedure */ /* Initiate Paging procedure */
LOG_TRANS(trans, LOGL_DEBUG, "Initiating Paging due to MMSMS_EST_REQ\n"); LOG_TRANS(trans, LOGL_DEBUG, "Initiating Paging due to MMSMS_EST_REQ\n");
trans->paging_request = subscr_request_conn(trans->vsub, trans->paging_request = paging_request_start(trans->vsub, PAGING_CAUSE_SIGNALLING_LOW_PRIO,
paging_cb_mmsms_est_req, mmsms_paging_cb, trans, "MT-SMS");
trans, "MT SMS",
SGSAP_SERV_IND_SMS);
if (!trans->paging_request) { if (!trans->paging_request) {
LOG_TRANS(trans, LOGL_ERROR, "Failed to initiate Paging\n"); LOG_TRANS(trans, LOGL_ERROR, "Failed to initiate Paging\n");
/* Inform SMC about channel establishment failure */ /* Inform SMC about channel establishment failure */
gsm411_smc_recv(&trans->sms.smc_inst, gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_REL_IND, NULL, 0);
GSM411_MMSMS_REL_IND, NULL, 0);
trans_free(trans); trans_free(trans);
return -EIO; 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)); gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
/* Outgoing needs the highest bit set */ /* 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; gh->msg_type = msg_type;
OMSC_LINKID_CB(msg) = trans->dlci; 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) static int sms_route_mt_sms(struct gsm_trans *trans, struct gsm_sms *gsms)
{ {
int rc; 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 #ifdef BUILD_SMPP
int smpp_first = smpp_route_smpp_first(gsms, conn);
/* /*
* Route through SMPP first before going to the local database. In case * Route through SMPP first before going to the local database. In case
* of a unroutable message and no local subscriber, SMPP will be tried * of a unroutable message and no local subscriber, SMPP will be tried
* twice. In case of an unknown subscriber continue with the normal * twice. In case of an unknown subscriber continue with the normal
* delivery of the SMS. * delivery of the SMS.
*/ */
if (smpp_first) { if (smpp_route_smpp_first()) {
rc = smpp_try_deliver(gsms, conn); rc = smpp_try_deliver(gsms, msc_a);
if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED)
/* unknown subscriber, try local */ /* unknown subscriber, try local */
goto 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); LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc);
rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
/* rc will be logged by gsm411_send_rp_error() */ /* rc will be logged by gsm411_send_rp_error() */
rate_ctr_inc(&conn->network->msc_ctrs->ctr[ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
} }
return rc; return rc;
} }
@ -438,28 +416,27 @@ try_local:
#endif #endif
/* determine gsms->receiver based on dialled number */ /* 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) { if (!gsms->receiver) {
#ifdef BUILD_SMPP #ifdef BUILD_SMPP
/* Avoid a second look-up */ /* Avoid a second look-up */
if (smpp_first) { if (smpp_route_smpp_first()) {
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]);
return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; 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) { 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) { } else if (rc < 0) {
LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc); LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc);
rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
/* rc will be logged by gsm411_send_rp_error() */ /* rc will be logged by gsm411_send_rp_error() */
rate_ctr_inc(&conn->network->msc_ctrs->ctr[ rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]);
} }
#else #else
rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; 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 #endif
} else } else
rc = 0; rc = 0;
@ -473,7 +450,6 @@ try_local:
static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg, static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
uint32_t gsm411_msg_ref) uint32_t gsm411_msg_ref)
{ {
struct ran_conn *conn = trans->conn;
uint8_t *smsp = msgb_sms(msg); uint8_t *smsp = msgb_sms(msg);
struct gsm_sms *gsms; struct gsm_sms *gsms;
unsigned int sms_alphabet; 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 da_len_bytes;
uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
int rc = 0; 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(); gsms = sms_alloc();
if (!gsms) if (!gsms)
@ -579,7 +561,7 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
} }
} }
OSMO_STRLCPY_ARRAY(gsms->src.addr, conn->vsub->msisdn); OSMO_STRLCPY_ARRAY(gsms->src.addr, vsub->msisdn);
LOG_TRANS(trans, LOGL_INFO, LOG_TRANS(trans, LOGL_INFO,
"MO SMS -- MTI: 0x%02x, VPF: 0x%02x, " "MO SMS -- MTI: 0x%02x, VPF: 0x%02x, "
@ -593,9 +575,6 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg,
gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp); 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); rc = sms_route_mt_sms(trans, gsms);
/* /*
@ -824,7 +803,8 @@ static int gsm411_rx_rp_ack(struct gsm_trans *trans,
static int gsm411_rx_rp_error(struct gsm_trans *trans, static int gsm411_rx_rp_error(struct gsm_trans *trans,
struct gsm411_rp_hdr *rph) 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; struct gsm_sms *sms = trans->sms.sms;
uint8_t cause_len = rph->data[0]; uint8_t cause_len = rph->data[0];
uint8_t cause = rph->data[1]; uint8_t cause = rph->data[1];
@ -1005,11 +985,11 @@ static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type,
return rc; return rc;
} }
static struct gsm_trans *gsm411_trans_init(struct gsm_network *net, struct vlr_subscr *vsub, struct ran_conn *conn, static struct gsm_trans *gsm411_trans_init(struct gsm_network *net, struct vlr_subscr *vsub, struct msc_a *msc_a,
uint8_t tid) uint8_t tid, bool mo)
{ {
/* Allocate a new transaction */ /* 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) { if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, "No memory for transaction\n"); LOG_TRANS(trans, LOGL_ERROR, "No memory for transaction\n");
return NULL; return NULL;
@ -1019,9 +999,24 @@ static struct gsm_trans *gsm411_trans_init(struct gsm_network *net, struct vlr_s
gsm411_smc_init(&trans->sms.smc_inst, 0, 1, gsm411_mn_recv, gsm411_mm_send); gsm411_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); gsm411_smr_init(&trans->sms.smr_inst, 0, 1, gsm411_rl_recv, gsm411_mn_send);
/* Associate transaction with connection */ if (msc_a) {
if (conn) msc_a_get(msc_a, MSC_A_USE_SMS);
trans->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_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; return trans;
} }
@ -1053,22 +1048,22 @@ static int gsm411_assign_sm_rp_mr(struct gsm_trans *trans)
static struct gsm_trans *gsm411_alloc_mt_trans(struct gsm_network *net, static struct gsm_trans *gsm411_alloc_mt_trans(struct gsm_network *net,
struct vlr_subscr *vsub) struct vlr_subscr *vsub)
{ {
struct ran_conn *conn; struct msc_a *msc_a;
struct gsm_trans *trans = NULL; struct gsm_trans *trans = NULL;
int tid; int tid;
/* Generate a new transaction ID */ /* 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) { if (tid == -1) {
LOG_TRANS(trans, LOGL_ERROR, "No available transaction IDs\n"); LOG_TRANS(trans, LOGL_ERROR, "No available transaction IDs\n");
return NULL; return NULL;
} }
/* Attempt to find an existing connection */ /* Attempt to find an existing connection */
conn = connection_for_subscr(vsub); msc_a = msc_a_for_vsub(vsub, true);
/* Allocate a new transaction */ /* Allocate a new transaction */
trans = gsm411_trans_init(net, vsub, conn, tid); trans = gsm411_trans_init(net, vsub, msc_a, tid, false);
if (!trans) if (!trans)
return NULL; return NULL;
@ -1197,8 +1192,7 @@ int gsm411_send_rp_data(struct gsm_network *net, struct vlr_subscr *vsub,
} }
/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */ /* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
int gsm0411_rcv_sms(struct ran_conn *conn, int gsm0411_rcv_sms(struct msc_a *msc_a, struct msgb *msg)
struct msgb *msg)
{ {
struct gsm48_hdr *gh = msgb_l3(msg); struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t msg_type = gh->msg_type; uint8_t msg_type = gh->msg_type;
@ -1207,12 +1201,10 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
struct gsm_trans *trans; struct gsm_trans *trans;
int new_trans = 0; int new_trans = 0;
int rc = 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) trans = trans_find_by_id(msc_a, TRANS_SMS, transaction_id);
return -EIO;
/* FIXME: send some error message */
trans = trans_find_by_id(conn, GSM48_PDISC_SMS, transaction_id);
/* /*
* A transaction we created but don't know about? * A transaction we created but don't know about?
@ -1226,7 +1218,8 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
} }
if (!trans) { 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) { if (!trans) {
/* FIXME: send some error message */ /* FIXME: send some error message */
return -ENOMEM; return -ENOMEM;
@ -1234,9 +1227,6 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
trans->sms.sm_rp_mr = rph->msg_ref; /* SM-RP Message Reference */ trans->sms.sm_rp_mr = rph->msg_ref; /* SM-RP Message Reference */
trans->dlci = OMSC_LINKID_CB(msg); /* DLCI as received from BSC */ 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", LOG_TRANS(trans, LOGL_DEBUG, "receiving SMS message %s\n",
@ -1257,7 +1247,7 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
if (i == transaction_id) if (i == transaction_id)
continue; continue;
ptrans = trans_find_by_id(conn, GSM48_PDISC_SMS, i); ptrans = trans_find_by_id(msc_a, TRANS_SMS, i);
if (!ptrans) if (!ptrans)
continue; continue;
@ -1268,8 +1258,6 @@ int gsm0411_rcv_sms(struct ran_conn *conn,
} }
} }
ran_conn_communicating(conn);
gsm411_smc_recv(&trans->sms.smc_inst, gsm411_smc_recv(&trans->sms.smc_inst,
(new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND, (new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND,
msg, msg_type); msg, msg_type);
@ -1297,19 +1285,19 @@ void _gsm411_sms_trans_free(struct gsm_trans *trans)
} }
/* Process incoming SAPI N-REJECT from BSC */ /* 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_network *net;
struct gsm_trans *trans, *tmp; 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) { llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry) {
struct gsm_sms *sms; struct gsm_sms *sms;
if (trans->conn != conn) if (trans->msc_a != msc_a)
continue; continue;
if (trans->protocol != GSM48_PDISC_SMS) if (trans->type != TRANS_SMS)
continue; continue;
sms = trans->sms.sms; sms = trans->sms.sms;

View File

@ -31,6 +31,8 @@
#include <osmocom/msc/msc_common.h> #include <osmocom/msc/msc_common.h>
#include <osmocom/msc/debug.h> #include <osmocom/msc/debug.h>
#include <osmocom/msc/vlr.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 */ /* Common helper for preparing to be encoded GSUP message */
static void gsup_sm_msg_init(struct osmo_gsup_message *gsup_msg, 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) uint8_t *sm_rp_mr)
{ {
/* Init a mew GSUP message */ /* Init a mew GSUP message */
memset(gsup_msg, 0x00, sizeof(*gsup_msg)); *gsup_msg = (struct osmo_gsup_message){
gsup_msg->message_type = msg_type; .message_type = msg_type,
.sm_rp_mr = sm_rp_mr,
/* SM-RP-MR (Message Reference) */ .message_class = OSMO_GSUP_MESSAGE_CLASS_SMS,
gsup_msg->sm_rp_mr = sm_rp_mr; };
/* Fill in subscriber's IMSI */ /* Fill in subscriber's IMSI */
OSMO_STRLCPY_ARRAY(gsup_msg->imsi, 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_len = msgb_l4len(msg);
gsup_msg.sm_rp_ui = (uint8_t *) msgb_sms(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) 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 */ /* Indicate SMMA as the Alert Reason */
gsup_msg.sm_alert_rsn = OSMO_GSUP_SMS_SM_ALERT_RSN_MEM_AVAIL; 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 */ /* Triggers either RP-ACK or RP-ERROR on response from SMSC */
int gsm411_gsup_mo_handler(struct vlr_subscr *vsub, static int gsm411_gsup_mo_handler(struct vlr_subscr *vsub,
struct osmo_gsup_message *gsup_msg) const struct osmo_gsup_message *gsup_msg)
{ {
struct vlr_instance *vlr; struct vlr_instance *vlr;
struct gsm_network *net; 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, gsup_sm_msg_init(&gsup_msg, OSMO_GSUP_MSGT_MT_FORWARD_SM_RESULT,
trans->vsub->imsi, &sm_rp_mr); 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, 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; gsup_msg.sm_rp_cause = &cause;
/* TODO: include optional SM-RP-UI field if present */ /* 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) */ /* Handles MT SMS (and triggers Paging Request if required) */
int gsm411_gsup_mt_handler(struct vlr_subscr *vsub, static int gsm411_gsup_mt_handler(struct vlr_subscr *vsub,
struct osmo_gsup_message *gsup_msg) const struct osmo_gsup_message *gsup_msg)
{ {
struct vlr_instance *vlr; struct vlr_instance *vlr;
struct gsm_network *net; struct gsm_network *net;
@ -285,3 +287,34 @@ msg_error:
LOGP(DLSMS, LOGL_NOTICE, "RX malformed MT-forwardSM-Req\n"); LOGP(DLSMS, LOGL_NOTICE, "RX malformed MT-forwardSM-Req\n");
return -EINVAL; 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;
}
}

View File

@ -30,7 +30,7 @@
#include <osmocom/msc/gsm_data.h> #include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsm_subscriber.h> #include <osmocom/msc/gsm_subscriber.h>
#include <osmocom/msc/gsm_04_08.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/gsm48.h>
#include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/gsm_utils.h>
@ -51,21 +51,21 @@ static struct msgb *create_gsm0414_msg(uint8_t msg_type)
return msg; 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); 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 */ /* 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) enum gsm414_tch_loop_mode loop_mode)
{ {
struct msgb *msg = create_gsm0414_msg(GSM414_MT_CLOSE_TCH_LOOP_CMD); 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); subch = (loop_mode << 1);
msgb_put_u8(msg, subch); 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 */ /* 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 */ /* 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 */ /* 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 */ /* 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) uint8_t tested_devs)
{ {
struct msgb *msg = create_gsm0414_msg(GSM414_MT_TEST_INTERFACE); struct msgb *msg = create_gsm0414_msg(GSM414_MT_TEST_INTERFACE);
msgb_put_u8(msg, tested_devs); 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 */ /* 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) uint8_t technology)
{ {
struct msgb *msg = create_gsm0414_msg(GSM414_MT_RESET_MS_POS_STORED); struct msgb *msg = create_gsm0414_msg(GSM414_MT_RESET_MS_POS_STORED);
msgb_put_u8(msg, technology); 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 */ /* 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 msgb *msg)
{ {
struct gsm48_hdr *gh = msgb_l3(msg); struct gsm48_hdr *gh = msgb_l3(msg);

View File

@ -26,7 +26,7 @@
#include <errno.h> #include <errno.h>
#include <osmocom/msc/gsm_04_80.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/protocol/gsm_04_80.h>
#include <osmocom/gsm/gsm0480.h> #include <osmocom/gsm/gsm0480.h>
@ -36,7 +36,7 @@
/*! Send a MT RELEASE COMPLETE message with Reject component /*! Send a MT RELEASE COMPLETE message with Reject component
* (see section 3.6.1) and given error code (see section 3.6.7). * (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] transaction_id Transaction ID with TI flag set
* \param[in] invoke_id InvokeID of the request * \param[in] invoke_id InvokeID of the request
* \param[in] problem_tag Problem code tag (table 3.13) * \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 * failed, any incorrect value can be passed (0x00 > x > 0xff), so
* the universal NULL-tag (see table 3.6) will be used instead. * the universal NULL-tag (see table 3.6) will be used instead.
*/ */
int msc_send_ussd_reject(struct ran_conn *conn, int msc_send_ussd_reject(struct msc_a *msc_a, uint8_t transaction_id, int invoke_id,
uint8_t transaction_id, int invoke_id, uint8_t problem_tag, uint8_t problem_code)
uint8_t problem_tag, uint8_t problem_code)
{ {
struct gsm48_hdr *gh; struct gsm48_hdr *gh;
struct msgb *msg; struct msgb *msg;
@ -67,27 +66,26 @@ int msc_send_ussd_reject(struct ran_conn *conn,
gh->proto_discr |= transaction_id << 4; gh->proto_discr |= transaction_id << 4;
gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; 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); struct msgb *msg = gsm0480_create_ussd_notify(level, text);
if (!msg) if (!msg)
return -1; 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, int msc_send_ussd_release_complete(struct msc_a *msc_a, uint8_t transaction_id)
uint8_t transaction_id)
{ {
struct msgb *msg = gsm0480_create_release_complete(transaction_id); struct msgb *msg = gsm0480_create_release_complete(transaction_id);
if (!msg) if (!msg)
return -1; 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 transaction_id,
uint8_t cause_loc, uint8_t cause_val) 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[2] = (1 << 7) | (0x03 << 5) | (cause_loc & 0x0f);
cause_ie[3] = (1 << 7) | cause_val; cause_ie[3] = (1 << 7) | cause_val;
return msc_tx_dtap(conn, msg); return msc_a_tx_dtap_to_i(msc_a, msg);
} }

View File

@ -46,7 +46,10 @@
#include <osmocom/msc/gsm_04_08.h> #include <osmocom/msc/gsm_04_08.h>
#include <osmocom/msc/transaction.h> #include <osmocom/msc/transaction.h>
#include <osmocom/gsupclient/gsup_client.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 */ /* FIXME: choose a proper range */
static uint32_t new_callref = 0x20000001; 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"); LOG_TRANS(trans, LOGL_NOTICE, "SS/USSD session timeout, releasing\n");
/* Indicate connection release to subscriber (if active) */ /* 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 */ /* 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); GSM48_CAUSE_LOC_PUN_S_LU, GSM48_CC_CAUSE_NORMAL_UNSPEC);
} }
/* Terminate GSUP session with EUSE */ /* Terminate GSUP session with EUSE */
gsup_msg.message_type = OSMO_GSUP_MSGT_PROC_SS_ERROR; 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,
};
OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi); OSMO_STRLCPY_ARRAY(gsup_msg.imsi, trans->vsub->imsi);
gsup_msg.session_state = OSMO_GSUP_SESSION_STATE_END; gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
gsup_msg.session_id = trans->callref;
gsup_msg.cause = GMM_CAUSE_NET_FAIL;
osmo_gsup_client_enc_send(trans->net->vlr->gsup_client, &gsup_msg);
/* Finally, release this transaction */ /* Finally, release this transaction */
trans_free(trans); trans_free(trans);
} }
/* Entry point for call independent MO SS messages */ /* 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 gsm48_hdr *gh = msgb_l3(msg);
struct osmo_gsup_message gsup_msg; struct osmo_gsup_message gsup_msg;
struct gsm_trans *trans; struct gsm_trans *trans;
struct msgb *gsup_msgb;
uint16_t facility_ie_len; uint16_t facility_ie_len;
uint8_t *facility_ie; uint8_t *facility_ie;
uint8_t tid; uint8_t tid;
uint8_t msg_type; uint8_t msg_type;
int rc; 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); msg_type = gsm48_hdr_msg_type(gh);
tid = gsm48_hdr_trans_id_flip_ti(gh); tid = gsm48_hdr_trans_id_flip_ti(gh);
/* Associate logging messages with this subscriber */ /* 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 */ /* 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) { if (!trans) {
/* Count MS-initiated attempts to establish a NC SS/USSD session */ /* 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 * 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) { if (msg_type != GSM0480_MTYPE_REGISTER) {
LOG_TRANS(trans, LOGL_ERROR, "Rx wrong SS/USSD message type for new transaction: %s\n", 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_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type));
gsm48_tx_simple(conn, gsm48_tx_simple(msc_a,
GSM48_PDISC_NC_SS | (tid << 4), GSM48_PDISC_NC_SS | (tid << 4),
GSM0480_MTYPE_RELEASE_COMPLETE); GSM0480_MTYPE_RELEASE_COMPLETE);
return -EINVAL; return -EINVAL;
} }
trans = trans_alloc(conn->network, conn->vsub, trans = trans_alloc(net, vsub, TRANS_USSD, tid, new_callref++);
GSM48_PDISC_NC_SS, tid, new_callref++);
if (!trans) { if (!trans) {
LOG_TRANS(trans, LOGL_ERROR, " -> No memory for trans\n"); 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), GSM48_PDISC_NC_SS | (tid << 4),
GSM0480_MTYPE_RELEASE_COMPLETE); GSM0480_MTYPE_RELEASE_COMPLETE);
return -ENOMEM; return -ENOMEM;
@ -140,20 +157,28 @@ int gsm0911_rcv_nc_ss(struct ran_conn *conn, struct msgb *msg)
ncss_session_timeout_handler, trans); ncss_session_timeout_handler, trans);
/* Count active NC SS/USSD sessions */ /* 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); 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", LOG_TRANS(trans, LOGL_DEBUG, "Received SS/USSD msg %s\n",
gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type)); gsm48_pdisc_msgtype_name(GSM48_PDISC_NC_SS, msg_type));
/* (Re)schedule the inactivity timer */ /* (Re)schedule the inactivity timer */
if (conn->network->ncss_guard_timeout > 0) { if (net->ncss_guard_timeout > 0) {
osmo_timer_schedule(&trans->ss.timer_guard, osmo_timer_schedule(&trans->ss.timer_guard, net->ncss_guard_timeout, 0);
conn->network->ncss_guard_timeout, 0);
} }
/* Attempt to extract Facility IE */ /* 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 */ /* Compose a mew GSUP message */
memset(&gsup_msg, 0x00, sizeof(gsup_msg)); gsup_msg = (struct osmo_gsup_message){
gsup_msg.message_type = OSMO_GSUP_MSGT_PROC_SS_REQUEST; .message_type = OSMO_GSUP_MSGT_PROC_SS_REQUEST,
gsup_msg.session_id = trans->callref; .session_id = trans->callref,
.message_class = OSMO_GSUP_MESSAGE_CLASS_USSD,
};
/** /**
* Perform A-interface to GSUP-interface mapping, * 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 */ /* 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 */ rc = gsup_client_mux_tx(trans->net->gcm, &gsup_msg);
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;
}
/* Should we release connection? Or wait for response? */ /* Should we release connection? Or wait for response? */
if (msg_type == GSM0480_MTYPE_RELEASE_COMPLETE) if (msg_type == GSM0480_MTYPE_RELEASE_COMPLETE)
trans_free(trans); trans_free(trans);
else
ran_conn_communicating(conn);
/* Count established MS-initiated NC SS/USSD sessions */ /* Count established MS-initiated NC SS/USSD sessions */
if (msg_type == GSM0480_MTYPE_REGISTER) 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; return 0;
error: error:
/* Abort transaction on DTAP-interface */ /* 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_PROBLEM_CODE_TAG_GENERAL,
GSM_0480_GEN_PROB_CODE_UNRECOGNISED); GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
if (trans) if (trans)
@ -251,76 +256,69 @@ error:
} }
/* Call-back from paging the B-end of the connection */ /* Call-back from paging the B-end of the connection */
static int handle_paging_event(unsigned int hooknum, unsigned int event, static void ss_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans)
struct msgb *msg, void *_conn, void *_transt)
{ {
struct ran_conn *conn = _conn;
enum gsm_paging_event paging_event = event;
struct gsm_trans *transt = _transt;
struct gsm48_hdr *gh; struct gsm48_hdr *gh;
struct msgb *ss_msg; struct msgb *ss_msg;
OSMO_ASSERT(!transt->conn); if (trans->msc_a) {
OSMO_ASSERT(transt->ss.msg); 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) { if (msc_a) {
case GSM_PAGING_SUCCEEDED: struct gsm_network *net = msc_a_net(msc_a);
DEBUGP(DMM, "Paging subscr %s succeeded!\n", LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "Paging succeeded\n");
vlr_subscr_msisdn_or_name(transt->vsub));
/* Assign connection */ /* Assign connection */
transt->conn = ran_conn_get(conn, RAN_CONN_USE_TRANS_NC_SS); msc_a_get(msc_a, MSC_A_USE_NC_SS);
transt->paging_request = NULL; trans->msc_a = msc_a;
trans->paging_request = NULL;
/* (Re)schedule the inactivity timer */ /* (Re)schedule the inactivity timer */
if (conn->network->ncss_guard_timeout > 0) { if (net->ncss_guard_timeout > 0) {
osmo_timer_schedule(&transt->ss.timer_guard, osmo_timer_schedule(&trans->ss.timer_guard, net->ncss_guard_timeout, 0);
conn->network->ncss_guard_timeout, 0);
} }
/* Send stored message */ /* Send stored message */
ss_msg = transt->ss.msg; ss_msg = trans->ss.msg;
gh = (struct gsm48_hdr *) msgb_push(ss_msg, sizeof(*gh)); gh = (struct gsm48_hdr *) msgb_push(ss_msg, sizeof(*gh));
gh->proto_discr = GSM48_PDISC_NC_SS; 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; gh->msg_type = GSM0480_MTYPE_REGISTER;
/* Sent to the MS, give ownership of ss_msg */ /* Sent to the MS, give ownership of ss_msg */
msc_tx_dtap(transt->conn, ss_msg); msc_a_tx_dtap_to_i(msc_a, ss_msg);
transt->ss.msg = NULL; trans->ss.msg = NULL;
/* Count established network-initiated NC SS/USSD sessions */ /* Count established network-initiated NC SS/USSD sessions */
rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]); rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]);
break; } else {
case GSM_PAGING_EXPIRED: LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "Paging expired\n");
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");
/* TODO: inform HLR about this failure */ /* TODO: inform HLR about this failure */
msgb_free(transt->ss.msg); msgb_free(trans->ss.msg);
transt->ss.msg = NULL; trans->ss.msg = NULL;
transt->callref = 0; trans->callref = 0;
transt->paging_request = NULL; trans->paging_request = NULL;
trans_free(transt); trans_free(trans);
break;
} }
return 0;
} }
static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net, 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; struct gsm_trans *trans, *transt;
int tid; int tid;
/* Allocate transaction first, for log context */ /* 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); TRANS_ID_UNASSIGNED, gsup_msg->session_id);
if (!trans) { 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); osmo_counter_inc(net->active_nc_ss);
/* Assign transaction ID */ /* 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) { if (tid < 0) {
LOG_TRANS(trans, LOGL_ERROR, "No free transaction ID\n"); LOG_TRANS(trans, LOGL_ERROR, "No free transaction ID\n");
/* TODO: inform HLR about this */ /* 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); ncss_session_timeout_handler, trans);
/* Attempt to find connection */ /* Attempt to find connection */
conn = connection_for_subscr(vsub); msc_a = msc_a_for_vsub(vsub, true);
if (conn) { if (msc_a) {
/* Assign connection */ /* 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 */ trans->dlci = 0x00; /* SAPI=0, not SACCH */
return trans; 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, " LOG_TRANS(trans, LOGL_ERROR, "Paging already started, "
"rejecting message...\n"); "rejecting message...\n");
trans_free(trans); 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; return NULL;
} }
/* Trigger Paging Request */ /* Trigger Paging Request */
trans->paging_request = subscr_request_conn(vsub, trans->paging_request = paging_request_start(vsub, PAGING_CAUSE_SIGNALLING_HIGH_PRIO,
&handle_paging_event, trans, "GSM 09.11 SS/USSD", ss_paging_cb, trans, "GSM 09.11 SS/USSD");
SGSAP_SERV_IND_CS_CALL);
if (!trans->paging_request) { if (!trans->paging_request) {
LOG_TRANS(trans, LOGL_ERROR, "Failed to allocate paging token\n"); LOG_TRANS(trans, LOGL_ERROR, "Failed to allocate paging token\n");
trans_free(trans); 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); osmo_counter_dec(trans->net->active_nc_ss);
} }
int gsm0911_gsup_handler(struct vlr_subscr *vsub, int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg)
struct osmo_gsup_message *gsup_msg)
{ {
struct vlr_instance *vlr; struct vlr_instance *vlr = data;
struct gsm_network *net; struct gsm_network *net;
struct gsm_trans *trans; struct gsm_trans *trans;
struct gsm48_hdr *gh; struct gsm48_hdr *gh;
struct msgb *ss_msg; struct msgb *ss_msg;
bool trans_end; 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 */ /* Associate logging messages with this subscriber */
log_set_context(LOG_CTX_VLR_SUBSCR, vsub); 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); trans_end = (gh->msg_type == GSM0480_MTYPE_RELEASE_COMPLETE);
/* Sent to the MS, give ownership of ss_msg */ /* 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 */ /* Release transaction if required */
if (trans_end) if (trans_end)

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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

View File

@ -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;
}

View File

@ -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 */

View File

@ -286,3 +286,103 @@ int mncc_prim_check(const struct gsm_mncc *mncc_prim, unsigned int len)
} }
return 0; 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;
}

View File

@ -79,7 +79,7 @@ static int mncc_setup_ind(struct gsm_call *call, int msg_type,
/* already have remote call */ /* already have remote call */
if (call->remote_ref) if (call->remote_ref)
return 0; return 0;
/* transfer mode 1 would be packet mode, which was never specified */ /* transfer mode 1 would be packet mode, which was never specified */
if (setup->bearer_cap.mode != 0) { if (setup->bearer_cap.mode != 0) {
LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support " 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 */ /* Special messages */
switch(msg_type) { switch(msg_type) {
} }
/* find callref */ /* find callref */
callref = data->callref; callref = data->callref;
llist_for_each_entry(callt, &call_list, entry) { 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 */ /* create call */
if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) { if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
struct gsm_mncc rel; struct gsm_mncc rel;
memset(&rel, 0, sizeof(struct gsm_mncc)); memset(&rel, 0, sizeof(struct gsm_mncc));
rel.callref = callref; rel.callref = callref;
mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,

760
src/libmsc/mncc_call.c Normal file
View File

@ -0,0 +1,760 @@
/* Handle an MNCC managed call (external MNCC). */
/* At the time of writing, this is only used for inter-MSC handover: forward a voice stream to a remote MSC.
* Maybe it makes sense to also use it for all "normal" external call management at some point. */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h>
#include <osmocom/msc/mncc_call.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/rtp_stream.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/vlr.h>
struct osmo_fsm mncc_call_fsm;
static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call);
LLIST_HEAD(mncc_call_list);
static const struct osmo_tdef_state_timeout mncc_call_fsm_timeouts[32] = {
/* TODO */
};
struct gsm_network *gsmnet = NULL;
/* Transition to a state, using the T timer defined in msc_a_fsm_timeouts.
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable fi exists. */
#define mncc_call_fsm_state_chg(MNCC, STATE) \
osmo_tdef_fsm_inst_state_chg((MNCC)->fi, STATE, mncc_call_fsm_timeouts, gsmnet->mncc_tdefs, 5)
#define mncc_call_error(MNCC, FMT, ARGS...) do { \
LOG_MNCC_CALL(MNCC, LOGL_ERROR, FMT, ##ARGS); \
osmo_fsm_inst_term((MNCC)->fi, OSMO_FSM_TERM_REGULAR, 0); \
} while(0)
void mncc_call_fsm_init(struct gsm_network *net)
{
osmo_fsm_register(&mncc_call_fsm);
gsmnet = net;
}
void mncc_call_fsm_update_id(struct mncc_call *mncc_call)
{
osmo_fsm_inst_update_id_f_sanitize(mncc_call->fi, '-', "%s:callref-0x%x%s%s",
vlr_subscr_name(mncc_call->vsub), mncc_call->callref,
mncc_call->remote_msisdn_present ? ":to-msisdn-" : "",
mncc_call->remote_msisdn_present ? mncc_call->remote_msisdn.number : "");
}
/* Invoked by the socket read callback in case the given MNCC call instance is responsible for the given callref. */
void mncc_call_rx(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
{
if (!mncc_call)
return;
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Rx %s\n", get_mncc_name(mncc_msg->msg_type));
osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_RX_MNCC_MSG, (void*)mncc_msg);
}
/* Send an MNCC message (associated with this MNCC call). */
int mncc_call_tx(struct mncc_call *mncc_call, union mncc_msg *mncc_msg)
{
struct msgb *msg;
unsigned char *data;
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "tx %s\n", get_mncc_name(mncc_msg->msg_type));
msg = msgb_alloc(sizeof(*mncc_msg), "MNCC-tx");
OSMO_ASSERT(msg);
data = msgb_put(msg, sizeof(*mncc_msg));
memcpy(data, mncc_msg, sizeof(*mncc_msg));
if (gsmnet->mncc_recv(gsmnet, msg)) {
mncc_call_error(mncc_call, "Failed to send MNCC message %s\n", get_mncc_name(mncc_msg->msg_type));
return -EIO;
}
return 0;
}
/* Send a trivial MNCC message with just a message type (associated with this MNCC call). */
int mncc_call_tx_msgt(struct mncc_call *mncc_call, uint32_t msg_type)
{
union mncc_msg mncc_msg = {
.signal = {
.msg_type = msg_type,
.callref = mncc_call->callref,
},
};
return mncc_call_tx(mncc_call, &mncc_msg);
}
/* Allocate an MNCC FSM as child of the given MSC role FSM.
* parent_event_call_released is mandatory and is passed as the parent_term_event.
* parent_event_call_setup_complete is dispatched when the MNCC FSM enters the MNCC_CALL_ST_TALKING state.
* parent_event_call_setup_complete is optional, pass a negative number to avoid dispatching.
*
* If non-NULL, message_cb is invoked whenever an MNCC message is received from the the MNCC socket, which is useful to
* forward things like DTMF to CC or to another MNCC call.
*
* After mncc_call_alloc(), call either mncc_call_outgoing_start() or mncc_call_incoming_start().
*/
struct mncc_call *mncc_call_alloc(struct vlr_subscr *vsub,
struct osmo_fsm_inst *parent,
int parent_event_call_setup_complete,
uint32_t parent_event_call_released,
mncc_call_message_cb_t message_cb, void *forward_cb_data)
{
struct mncc_call *mncc_call;
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&mncc_call_fsm, parent, parent_event_call_released);
OSMO_ASSERT(fi);
OSMO_ASSERT(vsub);
mncc_call = talloc(fi, struct mncc_call);
OSMO_ASSERT(mncc_call);
fi->priv = mncc_call;
*mncc_call = (struct mncc_call){
.fi = fi,
.vsub = vsub,
.parent_event_call_setup_complete = parent_event_call_setup_complete,
.message_cb = message_cb,
.forward_cb_data = forward_cb_data,
};
llist_add(&mncc_call->entry, &mncc_call_list);
mncc_call_fsm_update_id(mncc_call);
return mncc_call;
}
void mncc_call_reparent(struct mncc_call *mncc_call,
struct osmo_fsm_inst *new_parent,
int parent_event_call_setup_complete,
uint32_t parent_event_call_released,
mncc_call_message_cb_t message_cb, void *forward_cb_data)
{
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n",
mncc_call->fi->proc.parent->name, new_parent->name);
osmo_fsm_inst_change_parent(mncc_call->fi, new_parent, parent_event_call_released);
talloc_steal(new_parent, mncc_call->fi);
mncc_call->parent_event_call_setup_complete = parent_event_call_setup_complete;
mncc_call->message_cb = message_cb;
mncc_call->forward_cb_data = forward_cb_data;
}
/* Associate an rtp_stream with this MNCC call instance (optional).
* Can be called directly after mncc_call_alloc(). If an rtp_stream is set, upon receiving the MNCC_RTP_CONNECT containing
* the PBX's RTP IP and port, pass the IP:port information to rtp_stream_set_remote_addr() and rtp_stream_commit() to
* update the MGW connection. If no rtp_stream is associated, the caller is responsible to manually extract the RTP
* IP:port from the MNCC_RTP_CONNECT message forwarded to mncc_call_message_cb_t (see mncc_call_alloc()).
* When an rtp_stream is set, call rtp_stream_release() when the MNCC call ends; call mncc_call_detach_rtp_stream() before
* the MNCC call releases if that is not desired.
*/
int mncc_call_set_rtp_stream(struct mncc_call *mncc_call, struct rtp_stream *rtps)
{
if (mncc_call->rtps && mncc_call->rtps != rtps) {
LOG_MNCC_CALL(mncc_call, LOGL_ERROR,
"Cannot associate with RTP stream %s, already associated with %s\n",
rtps ? rtps->fi->name : "NULL", mncc_call->rtps->fi->name);
return -ENOSPC;
}
mncc_call->rtps = rtps;
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Associated with RTP stream %s\n", mncc_call->rtps->fi->name);
return 0;
}
/* Disassociate the rtp_stream from this MNCC call instance, and clear the remote RTP IP:port info.
* When the MNCC FSM ends for any reason, it will release the RTP stream (which usually triggers complete tear down of
* the call_leg and CC transaction). If the RTP stream should still remain in use, e.g. during Subseqent inter-MSC
* Handover where this MNCC was a forwarding to a remote MSC that is no longer needed, this function must be called
* before the MNCC FSM instance terminates. Call this *before* setting a new remote RTP address on the rtp_stream, since
* this clears the rtp_stream->remote ip:port information. */
void mncc_call_detach_rtp_stream(struct mncc_call *mncc_call)
{
struct rtp_stream *rtps = mncc_call->rtps;
struct osmo_sockaddr_str clear;
if (!rtps)
return;
mncc_call->rtps = NULL;
rtp_stream_set_remote_addr(rtps, &clear);
}
static void mncc_call_tx_setup_ind(struct mncc_call *mncc_call)
{
struct gsm_mncc mncc_msg = mncc_call->outgoing_req;
mncc_msg.msg_type = MNCC_SETUP_IND;
mncc_msg.callref = mncc_call->callref;
OSMO_STRLCPY_ARRAY(mncc_msg.imsi, mncc_call->vsub->imsi);
if (!(mncc_call->outgoing_req.fields & MNCC_F_CALLING)) {
/* No explicit calling number set, use the local subscriber */
mncc_msg.fields |= MNCC_F_CALLING;
OSMO_STRLCPY_ARRAY(mncc_msg.calling.number, mncc_call->vsub->msisdn);
}
mncc_call->local_msisdn_present = true;
mncc_call->local_msisdn = mncc_msg.calling;
rate_ctr_inc(&gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]);
mncc_call_tx(mncc_call, (union mncc_msg*)&mncc_msg);
}
static void mncc_call_rx_setup_req(struct mncc_call *mncc_call, const struct gsm_mncc *incoming_req)
{
mncc_call->callref = incoming_req->callref;
if (incoming_req->fields & MNCC_F_CALLED) {
mncc_call->local_msisdn_present = true;
mncc_call->local_msisdn = incoming_req->called;
}
if (incoming_req->fields & MNCC_F_CALLING) {
mncc_call->remote_msisdn_present = true;
mncc_call->remote_msisdn = incoming_req->calling;
}
mncc_call_fsm_update_id(mncc_call);
}
/* Remote PBX asks for RTP_CREATE. This merely asks us to create an RTP stream, and does not actually contain any useful
* information like the remote RTP IP:port (these follow in the RTP_CONNECT from the SIP side) */
static bool mncc_call_rx_rtp_create(struct mncc_call *mncc_call)
{
mncc_call->received_rtp_create = true;
if (!mncc_call->rtps) {
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but no RTP stream associated\n");
return true;
}
if (!osmo_sockaddr_str_is_set(&mncc_call->rtps->local)) {
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no local address\n");
return true;
}
if (!mncc_call->rtps->codec_known) {
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no codec set\n");
return true;
}
LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, responding with " OSMO_SOCKADDR_STR_FMT " %s\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&mncc_call->rtps->local),
osmo_mgcpc_codec_name(mncc_call->rtps->codec));
/* Already know what RTP IP:port to tell the MNCC. Send it. */
return mncc_call_tx_rtp_create(mncc_call);
}
/* Convert enum mgcp_codecs to an gsm_mncc_rtp->payload_msg_type value. */
uint32_t mgcp_codec_to_mncc_payload_msg_type(enum mgcp_codecs codec)
{
switch (codec) {
default:
/* disclaimer: i have no idea what i'm doing. */
case CODEC_GSM_8000_1:
return GSM_TCHF_FRAME;
case CODEC_GSMEFR_8000_1:
return GSM_TCHF_FRAME_EFR;
case CODEC_GSMHR_8000_1:
return GSM_TCHH_FRAME;
case CODEC_AMR_8000_1:
case CODEC_AMRWB_16000_1:
//return GSM_TCHF_FRAME;
return GSM_TCH_FRAME_AMR;
}
}
static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call)
{
if (!mncc_call->rtps || !osmo_sockaddr_str_is_set(&mncc_call->rtps->local)) {
mncc_call_error(mncc_call, "Cannot send RTP_CREATE, no local RTP address set up\n");
return false;
}
struct osmo_sockaddr_str *rtp_local = &mncc_call->rtps->local;
union mncc_msg mncc_msg = {
.rtp = {
.msg_type = MNCC_RTP_CREATE,
.callref = mncc_call->callref,
.port = rtp_local->port,
},
};
if (osmo_sockaddr_str_to_32n(rtp_local, &mncc_msg.rtp.ip)) {
mncc_call_error(mncc_call, "Failed to compose IP address " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(rtp_local));
return false;
}
if (mncc_call->rtps->codec_known) {
mncc_msg.rtp.payload_type = 0; /* ??? */
mncc_msg.rtp.payload_msg_type = mgcp_codec_to_mncc_payload_msg_type(mncc_call->rtps->codec);
}
if (mncc_call_tx(mncc_call, &mncc_msg))
return false;
return true;
}
static bool mncc_call_rx_rtp_connect(struct mncc_call *mncc_call, const struct gsm_mncc_rtp *mncc_msg)
{
struct osmo_sockaddr_str rtp;
if (!mncc_call->rtps) {
/* The user has not associated an RTP stream, hence we're not supposed to take any action here. */
return true;
}
if (osmo_sockaddr_str_from_32n(&rtp, mncc_msg->ip, mncc_msg->port)) {
mncc_call_error(mncc_call, "Cannot RTP-CONNECT, invalid RTP IP:port in incoming MNCC message\n");
return false;
}
rtp_stream_set_remote_addr(mncc_call->rtps, &rtp);
if (rtp_stream_commit(mncc_call->rtps)) {
mncc_call_error(mncc_call, "RTP-CONNECT, failed, RTP stream is not properly set up: %s\n",
mncc_call->rtps->fi->id);
return false;
}
return true;
}
/* Return true if the FSM instance still exists after this call, false if it was terminated. */
static bool mncc_call_rx_release_msg(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
{
switch (mncc_msg->msg_type) {
case MNCC_DISC_REQ:
/* Remote call leg ended the call, MNCC tells us to DISC. We ack with a REL. */
mncc_call_tx_msgt(mncc_call, MNCC_REL_IND);
osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, 0);
return false;
case MNCC_REL_REQ:
/* MNCC acks with a REL to a previous DISC IND we have (probably) sent.
* We ack with a REL CNF. */
mncc_call_tx_msgt(mncc_call, MNCC_REL_CNF);
osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, 0);
return false;
default:
return true;
}
}
/* Return true if the FSM instance still exists after this call, false if it was terminated. */
static bool mncc_call_rx_common_msg(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
{
switch (mncc_msg->msg_type) {
case MNCC_RTP_CREATE:
mncc_call_rx_rtp_create(mncc_call);
return true;
case MNCC_RTP_CONNECT:
mncc_call_rx_rtp_connect(mncc_call, &mncc_msg->rtp);
return true;
default:
return mncc_call_rx_release_msg(mncc_call, mncc_msg);
}
}
static void mncc_call_forward(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg)
{
if (!mncc_call || !mncc_call->message_cb)
return;
mncc_call->message_cb(mncc_call, mncc_msg, mncc_call->forward_cb_data);
}
/* Initiate an outgoing call.
* The outgoing_req represents the details for the MNCC_SETUP_IND message sent to initiate the outgoing call. Pass at
* least a called number (set outgoing_req->fields |= MNCC_F_CALLED and populate outgoing_req->called). All other items
* are optional and can be included if required. The message type, callref and IMSI from this struct are ignored,
* instead they are determined internally upon sending the MNCC message. If no calling number is set in the message
* struct, it will be set from mncc_call->vsub->msisdn.
*/
int mncc_call_outgoing_start(struct mncc_call *mncc_call, const struct gsm_mncc *outgoing_req)
{
if (!mncc_call)
return -EINVAL;
/* By dispatching an event instead of taking direct action, make sure that the FSM permits starting an outgoing
* call. */
return osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_OUTGOING_START, (void*)outgoing_req);
}
/* Handle an incoming call.
* When the MNCC recv callback (not included in this mncc_call_fsm API) detects an incoming call (MNCC_SETUP_REQ), take over
* handling of the incoming call by the given mncc_call instance.
* In incoming_req->setup_req_msg, pass the struct gsm_mncc message containing the received MNCC_SETUP_REQ.
* mncc_call_incoming_start() will immediately respond with a MNCC_CALL_CONF_IND; in incoming_req->bearer_cap, pass the
* bearer capabilities that should be included in this MNCC_CALL_CONF_IND message; in incoming_req->cccap, pass the
* CCCAP to be sent, if any.
*/
int mncc_call_incoming_start(struct mncc_call *mncc_call, const struct mncc_call_incoming_req *incoming_req)
{
if (!mncc_call)
return -EINVAL;
/* By dispatching an event instead of taking direct action, make sure that the FSM permits starting an incoming
* call. */
return osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_INCOMING_START, (void*)incoming_req);
}
static void mncc_call_incoming_tx_call_conf_ind(struct mncc_call *mncc_call, const struct gsm_mncc_bearer_cap *bearer_cap)
{
if (mncc_call->fi->state != MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) {
LOG_MNCC_CALL(mncc_call, LOGL_ERROR, "%s not allowed in this state\n", __func__);
return;
}
union mncc_msg mncc_msg = {
.signal = {
.msg_type = MNCC_CALL_CONF_IND,
.callref = mncc_call->callref,
},
};
if (bearer_cap) {
mncc_msg.signal.fields |= MNCC_F_BEARER_CAP;
mncc_msg.signal.bearer_cap = *bearer_cap;
}
mncc_call_tx(mncc_call, &mncc_msg);
}
/* Send an MNCC_SETUP_CNF message. Typically after the local side is ready to receive the call and RTP (e.g. for a GSM
* CC call, the lchan and RTP should be ready and the CC call should have been confirmed and alerting).
* For inter-MSC call forwarding, this can happen immediately upon the MNCC_RTP_CREATE.
*/
int mncc_call_incoming_tx_setup_cnf(struct mncc_call *mncc_call, const struct gsm_mncc_number *connected_number)
{
if (mncc_call->fi->state != MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) {
LOG_MNCC_CALL(mncc_call, LOGL_ERROR, "%s not allowed in this state\n", __func__);
return -EINVAL;
}
union mncc_msg mncc_msg = {
.signal = {
.msg_type = MNCC_SETUP_CNF,
.callref = mncc_call->callref,
},
};
if (connected_number) {
mncc_msg.signal.fields |= MNCC_F_CONNECTED;
mncc_msg.signal.connected = *connected_number;
}
return mncc_call_tx(mncc_call, &mncc_msg);
}
static void mncc_call_fsm_not_started(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mncc_call *mncc_call = fi->priv;
const struct gsm_mncc *outgoing_req;
const struct mncc_call_incoming_req *incoming_req;
switch (event) {
case MNCC_CALL_EV_OUTGOING_START:
outgoing_req = data;
mncc_call->outgoing_req = *outgoing_req;
mncc_call->callref = msc_cc_next_outgoing_callref();
mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING);
mncc_call_tx_setup_ind(mncc_call);
return;
case MNCC_CALL_EV_INCOMING_START:
incoming_req = data;
mncc_call_rx_setup_req(mncc_call, &incoming_req->setup_req_msg);
mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_INCOMING_WAIT_COMPLETE);
mncc_call_incoming_tx_call_conf_ind(mncc_call, incoming_req->bearer_cap_present ? &incoming_req->bearer_cap : NULL);
return;
default:
OSMO_ASSERT(false);
}
}
static void mncc_call_fsm_outgoing_wait_proceeding(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mncc_call *mncc_call = fi->priv;
const union mncc_msg *mncc_msg;
switch (event) {
case MNCC_CALL_EV_RX_MNCC_MSG:
mncc_msg = data;
if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
return;
switch (mncc_msg->msg_type) {
case MNCC_CALL_PROC_REQ:
mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE);
break;
default:
break;
}
mncc_call_forward(mncc_call, mncc_msg);
return;
default:
OSMO_ASSERT(false);
};
}
static void mncc_call_fsm_outgoing_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mncc_call *mncc_call = fi->priv;
const union mncc_msg *mncc_msg;
switch (event) {
case MNCC_CALL_EV_RX_MNCC_MSG:
mncc_msg = data;
if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
return;
switch (mncc_msg->msg_type) {
case MNCC_SETUP_RSP:
mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_TALKING);
mncc_call_tx_msgt(mncc_call, MNCC_SETUP_COMPL_IND);
break;
default:
break;
}
mncc_call_forward(mncc_call, mncc_msg);
return;
default:
OSMO_ASSERT(false);
};
}
static void mncc_call_fsm_incoming_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mncc_call *mncc_call = fi->priv;
const union mncc_msg *mncc_msg;
switch (event) {
case MNCC_CALL_EV_RX_MNCC_MSG:
mncc_msg = data;
if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
return;
switch (mncc_msg->msg_type) {
case MNCC_SETUP_COMPL_REQ:
mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_TALKING);
break;
default:
break;
}
mncc_call_forward(mncc_call, mncc_msg);
return;
default:
OSMO_ASSERT(false);
};
}
static void mncc_call_fsm_talking(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mncc_call *mncc_call = fi->priv;
const union mncc_msg *mncc_msg;
switch (event) {
case MNCC_CALL_EV_RX_MNCC_MSG:
mncc_msg = data;
if (!mncc_call_rx_common_msg(mncc_call, mncc_msg))
return;
mncc_call_forward(mncc_call, mncc_msg);
return;
default:
OSMO_ASSERT(false);
};
}
static void mncc_call_fsm_wait_release_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct mncc_call *mncc_call = fi->priv;
const union mncc_msg *mncc_msg;
switch (event) {
case MNCC_CALL_EV_RX_MNCC_MSG:
mncc_msg = data;
if (!mncc_call_rx_release_msg(mncc_call, mncc_msg))
return;
mncc_call_forward(mncc_call, mncc_msg);
return;
default:
OSMO_ASSERT(false);
};
}
static void mncc_call_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct mncc_call *mncc_call = fi->priv;
switch (fi->state) {
case MNCC_CALL_ST_NOT_STARTED:
case MNCC_CALL_ST_WAIT_RELEASE_ACK:
break;
default:
/* Make sure we did indicate some sort of release */
mncc_call_tx_msgt(mncc_call, MNCC_REL_IND);
break;
}
/* Releasing the RTP stream should trigger completely tearing down the call leg as well as the CC transaction.
* In case of an inter-MSC handover where this MNCC connection is replaced by another MNCC / another BSC
* connection, the caller needs to detach the RTP stream from this FSM before terminating it. */
if (mncc_call->rtps) {
rtp_stream_release(mncc_call->rtps);
mncc_call->rtps = NULL;
}
llist_del(&mncc_call->entry);
}
static int mncc_call_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
return 1;
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state mncc_call_fsm_states[] = {
[MNCC_CALL_ST_NOT_STARTED] = {
.name = "NOT_STARTED",
.in_event_mask = 0
| S(MNCC_CALL_EV_OUTGOING_START)
| S(MNCC_CALL_EV_INCOMING_START)
,
.out_state_mask = 0
| S(MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING)
| S(MNCC_CALL_ST_INCOMING_WAIT_COMPLETE)
,
.action = mncc_call_fsm_not_started,
},
[MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING] = {
.name = "OUTGOING_WAIT_PROCEEDING",
.in_event_mask = 0
| S(MNCC_CALL_EV_RX_MNCC_MSG)
,
.out_state_mask = 0
| S(MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE)
| S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
,
.action = mncc_call_fsm_outgoing_wait_proceeding,
},
[MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE] = {
.name = "OUTGOING_WAIT_COMPLETE",
.in_event_mask = 0
| S(MNCC_CALL_EV_RX_MNCC_MSG)
,
.out_state_mask = 0
| S(MNCC_CALL_ST_TALKING)
| S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
,
.action = mncc_call_fsm_outgoing_wait_complete,
},
[MNCC_CALL_ST_INCOMING_WAIT_COMPLETE] = {
.name = "INCOMING_WAIT_COMPLETE",
.in_event_mask = 0
| S(MNCC_CALL_EV_RX_MNCC_MSG)
,
.out_state_mask = 0
| S(MNCC_CALL_ST_TALKING)
| S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
,
.action = mncc_call_fsm_incoming_wait_complete,
},
[MNCC_CALL_ST_TALKING] = {
.name = "TALKING",
.in_event_mask = 0
| S(MNCC_CALL_EV_RX_MNCC_MSG)
,
.out_state_mask = 0
| S(MNCC_CALL_ST_WAIT_RELEASE_ACK)
,
.action = mncc_call_fsm_talking,
},
[MNCC_CALL_ST_WAIT_RELEASE_ACK] = {
.name = "WAIT_RELEASE_ACK",
.in_event_mask = 0
| S(MNCC_CALL_EV_RX_MNCC_MSG)
,
.action = mncc_call_fsm_wait_release_ack,
},
};
static const struct value_string mncc_call_fsm_event_names[] = {
OSMO_VALUE_STRING(MNCC_CALL_EV_RX_MNCC_MSG),
OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_START),
OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_ALERTING),
OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_SETUP_COMPLETE),
OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_START),
OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_SETUP),
OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_SETUP_COMPLETE),
OSMO_VALUE_STRING(MNCC_CALL_EV_CN_RELEASE),
OSMO_VALUE_STRING(MNCC_CALL_EV_MS_RELEASE),
{}
};
struct osmo_fsm mncc_call_fsm = {
.name = "mncc_call",
.states = mncc_call_fsm_states,
.num_states = ARRAY_SIZE(mncc_call_fsm_states),
.log_subsys = DMNCC,
.event_names = mncc_call_fsm_event_names,
.timer_cb = mncc_call_fsm_timer_cb,
.cleanup = mncc_call_fsm_cleanup,
};
struct mncc_call *mncc_call_find_by_callref(uint32_t callref)
{
struct mncc_call *mncc_call;
llist_for_each_entry(mncc_call, &mncc_call_list, entry) {
if (mncc_call->callref == callref)
return mncc_call;
}
return NULL;
}
void mncc_call_release(struct mncc_call *mncc_call)
{
if (!mncc_call)
return;
mncc_call_tx_msgt(mncc_call, MNCC_DISC_IND);
mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_WAIT_RELEASE_ACK);
}

View File

@ -56,15 +56,6 @@ int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg)
if (net->mncc_state->conn_bfd.fd < 0) { if (net->mncc_state->conn_bfd.fd < 0) {
LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app " LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app "
"but socket is gone\n", get_mncc_name(msg_type)); "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 */ /* free the original message */
msgb_free(msg); msgb_free(msg);
return -1; return -1;
@ -92,7 +83,7 @@ static void mncc_sock_close(struct mncc_sock_state *state)
state->listen_bfd.when |= BSC_FD_READ; state->listen_bfd.when |= BSC_FD_READ;
/* release all exisitng calls */ /* release all exisitng calls */
gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC); gsm0408_clear_all_trans(state->net, TRANS_CC);
/* flush the queue */ /* flush the queue */
while (!llist_empty(&state->net->upqueue)) { while (!llist_empty(&state->net->upqueue)) {

1651
src/libmsc/msc_a.c Normal file

File diff suppressed because it is too large Load Diff

392
src/libmsc/msc_a_remote.c Normal file
View File

@ -0,0 +1,392 @@
/* The MSC-A role implementation variant that forwards requests to/from a remote MSC. */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <inttypes.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/msc_a_remote.h>
#include <osmocom/msc/msc_roles.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/msc_t.h>
#include <osmocom/msc/e_link.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/ran_peer.h>
static struct osmo_fsm msc_a_remote_fsm;
static struct msc_a *msc_a_remote_priv(struct osmo_fsm_inst *fi)
{
OSMO_ASSERT(fi);
OSMO_ASSERT(fi->fsm == &msc_a_remote_fsm);
OSMO_ASSERT(fi->priv);
return fi->priv;
}
/* The idea is that this msc_a role is event-compatible to the "real" msc_a.c FSM, but instead of acting on the events
* directly, it forwards the events to a remote MSC-A role, via E-over-GSUP.
*
* [MSC-A---------------------] [MSC-B---------------------]
* msc_a <-- msc_{i,t}_remote <---GSUP---- msc_a_remote <-- msc_{i,t} <--BSSMAP--- [BSS]
* ^you are here
*/
static int msc_a_remote_msg_up_to_remote_msc(struct msc_a *msc_a,
enum msc_role from_role,
enum osmo_gsup_message_type message_type,
struct an_apdu *an_apdu)
{
struct osmo_gsup_message m;
struct e_link *e = msc_a->c.remote_to;
if (!e) {
LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "No E link to remote MSC, cannot send AN-APDU\n");
return -1;
}
if (e_prep_gsup_msg(e, &m)) {
LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Error composing E-interface GSUP message\n");
return -1;
}
m.message_type = message_type;
if (an_apdu) {
if (gsup_msg_assign_an_apdu(&m, an_apdu)) {
LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Error composing E-interface GSUP message\n");
return -1;
}
}
return e_tx(e, &m);
}
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_t_remote ----GSUP---> msc_a_remote --> msc_t ---BSSMAP--> [BSS]
* ^you are here
*/
static void msc_a_remote_rx_gsup_to_msc_t(struct msc_a *msc_a, const struct osmo_gsup_message *gsup_msg)
{
uint32_t event;
struct an_apdu an_apdu;
switch (gsup_msg->message_type) {
case OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST:
event = MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST;
break;
case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST:
case OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST:
event = MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
break;
case OSMO_GSUP_MSGT_E_CLOSE:
case OSMO_GSUP_MSGT_E_ABORT:
case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
/* TODO: maybe some non-"normal" release with error cause? */
msc_a_release_cn(msc_a);
return;
default:
LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
return;
};
gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
msub_role_dispatch(msc_a->c.msub, MSC_ROLE_T, event, &an_apdu);
if (an_apdu.msg)
msgb_free(an_apdu.msg);
}
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
* ^you are here
*/
static void msc_a_remote_rx_gsup_to_msc_i(struct msc_a *msc_a, const struct osmo_gsup_message *gsup_msg)
{
uint32_t event;
struct an_apdu an_apdu;
switch (gsup_msg->message_type) {
case OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST:
event = MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
break;
case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_ERROR:
case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_RESULT:
event = MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE;
break;
case OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_RESULT:
case OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_ERROR:
event = MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT;
break;
case OSMO_GSUP_MSGT_E_CLOSE:
case OSMO_GSUP_MSGT_E_ABORT:
case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
/* TODO: maybe some non-"normal" release with error cause? */
msc_a_release_cn(msc_a);
return;
default:
LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
return;
};
gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
msub_role_dispatch(msc_a->c.msub, MSC_ROLE_I, event, &an_apdu);
if (an_apdu.msg)
msgb_free(an_apdu.msg);
}
static void msc_a_remote_send_handover_failure(struct msc_a *msc_a, enum gsm0808_cause cause)
{
struct ran_msg ran_enc_msg = {
.msg_type = RAN_MSG_HANDOVER_FAILURE,
.handover_failure = {
.cause = cause,
},
};
struct an_apdu an_apdu = {
.an_proto = msc_a->c.ran->an_proto,
.msg = msc_role_ran_encode(msc_a->c.fi, &ran_enc_msg),
};
if (!an_apdu.msg)
return;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T, OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR, &an_apdu);
msgb_free(an_apdu.msg);
return;
}
/* [MSC-A---------------------] [MSC-B---------------------]
* msc_a --> msc_{i,t}_remote ----GSUP---> msc_a_remote --> msc_{i,t} ---BSSMAP--> [BSS]
* ^you are here
*/
static void msc_a_remote_rx_gsup(struct msc_a *msc_a, const struct osmo_gsup_message *gsup_msg)
{
struct msc_t *msc_t = msc_a_msc_t(msc_a);
struct msc_i *msc_i = msc_a_msc_i(msc_a);
/* If starting a new Handover, this subscriber *must* be new and completely unattached. Create a new msc_t role
* to receive below event. */
if (gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) {
if (msc_t || msc_i) {
LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_ERROR,
"Already have an MSC-T or -I role, cannot Rx %s from remote MSC\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
msc_a_remote_send_handover_failure(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE);
return;
}
msc_t = msc_t_alloc_without_ran_peer(msc_a->c.msub, msc_a->c.ran);
}
/* We are on a remote MSC-B. If an msub has an MSC-T role, this is the remote target of a handover, and all
* messages from MSC-A *must* be intended for the MSC-T role. As soon as the Handover is successful, the MSC-T
* role disappears and an MSC-I role appears. */
if (msc_t) {
LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "Routing to MSC-T: %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
msc_a_remote_rx_gsup_to_msc_t(msc_a, gsup_msg);
} else if (msc_i) {
LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "Routing to MSC-I: %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
msc_a_remote_rx_gsup_to_msc_i(msc_a, gsup_msg);
} else {
LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_ERROR,
"No MSC-T nor MSC-I role present, cannot Rx GSUP %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
}
}
static void msc_a_remote_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_a *msc_a = msc_a_remote_priv(fi);
struct an_apdu *an_apdu;
switch (event) {
case MSC_REMOTE_EV_RX_GSUP:
/* [MSC-A---------------------] [MSC-B---------------------]
* msc_a --> msc_{i,t}_remote ----GSUP---> msc_a_remote --> msc_{i,t} ---BSSMAP--> [BSS]
* ^you are here
*/
msc_a_remote_rx_gsup(msc_a, (const struct osmo_gsup_message*)data);
return;
/* For all remaining cases:
* [MSC-A---------------------] [MSC-B---------------------]
* msc_a <-- msc_{i,t}_remote <---GSUP---- msc_a_remote <-- msc_{i,t} <--BSSMAP--- [BSS]
* you are here^
*/
case MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I,
OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST, an_apdu);
return;
case MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I,
OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_REQUEST, an_apdu);
return;
case MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I,
OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST, an_apdu);
return;
case MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_RESULT, an_apdu);
return;
case MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR, an_apdu);
return;
case MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST, an_apdu);
return;
case MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST:
an_apdu = data;
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T,
OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST, an_apdu);
return;
case MSC_A_EV_CN_CLOSE:
case MSC_A_EV_MO_CLOSE:
osmo_fsm_inst_state_chg(msc_a->c.fi, MSC_A_ST_RELEASING, 0, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static void msc_a_remote_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
}
static void msc_a_remote_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct msc_a *msc_a = msc_a_remote_priv(fi);
if (msc_a->c.msub->role[MSC_ROLE_I])
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_I, OSMO_GSUP_MSGT_E_CLOSE, NULL);
if (msc_a->c.msub->role[MSC_ROLE_T])
msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T, OSMO_GSUP_MSGT_E_CLOSE, NULL);
}
#define S(x) (1 << (x))
/* FSM events are by definition compatible with msc_a_fsm. States could be a separate enum, but so that
* msc_a_is_accepted() also works on remote msc_a, this FSM shares state numbers with the msc_a_fsm_states. */
static const struct osmo_fsm_state msc_a_remote_fsm_states[] = {
/* Whichever MSC_A_ST would be the first for the real MSC-A implementation, a fresh FSM instance will start in
* state == 0 and we just need to be able to transition out of it. */
[0] = {
.name = "INIT-REMOTE",
.out_state_mask = 0
| S(MSC_A_ST_COMMUNICATING)
| S(MSC_A_ST_RELEASING)
,
},
[MSC_A_ST_COMMUNICATING] = {
.name = "COMMUNICATING",
.action = msc_a_remote_fsm_communicating,
.in_event_mask = 0
| S(MSC_REMOTE_EV_RX_GSUP)
| S(MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST)
| S(MSC_A_EV_FROM_I_PREPARE_SUBSEQUENT_HANDOVER_REQUEST)
| S(MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST)
| S(MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE)
| S(MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE)
| S(MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST)
| S(MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST)
| S(MSC_A_EV_CN_CLOSE)
| S(MSC_A_EV_MO_CLOSE)
,
.out_state_mask = 0
| S(MSC_A_ST_RELEASING)
,
},
[MSC_A_ST_RELEASING] = {
.name = "RELEASING",
.onenter = msc_a_remote_fsm_releasing_onenter,
},
};
static struct osmo_fsm msc_a_remote_fsm = {
.name = "msc_a_remote",
.states = msc_a_remote_fsm_states,
.num_states = ARRAY_SIZE(msc_a_remote_fsm_states),
.log_subsys = DMSC,
.event_names = msc_a_fsm_event_names,
.cleanup = msc_a_remote_fsm_cleanup,
};
static __attribute__((constructor)) void msc_a_remote_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&msc_a_remote_fsm) == 0);
}
struct msc_a *msc_a_remote_alloc(struct msub *msub, struct ran_infra *ran,
const uint8_t *remote_msc_name, size_t remote_msc_name_len)
{
struct msc_a *msc_a;
msub_role_alloc(msub, MSC_ROLE_A, &msc_a_remote_fsm, struct msc_a, ran);
msc_a = msub_msc_a(msub);
if (!msc_a) {
LOG_MSUB(msub, LOGL_ERROR, "Error setting up MSC-A remote role\n");
return NULL;
}
msc_a->c.remote_to = e_link_alloc(msub_net(msub)->gcm, msc_a->c.fi, remote_msc_name, remote_msc_name_len);
if (!msc_a->c.remote_to) {
LOG_MSC_A_REMOTE(msc_a, LOGL_ERROR, "Failed to set up E link\n");
msc_a_release_cn(msc_a);
return NULL;
}
msc_a_update_id(msc_a);
/* Immediately get out of state 0. */
osmo_fsm_inst_state_chg(msc_a->c.fi, MSC_A_ST_COMMUNICATING, 0, 0);
return msc_a;
}

879
src/libmsc/msc_ho.c Normal file
View File

@ -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,
};

383
src/libmsc/msc_i.c Normal file
View File

@ -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);
}

245
src/libmsc/msc_i_remote.c Normal file
View File

@ -0,0 +1,245 @@
/* The MSC-I role implementation variant that forwards requests to/from a remote MSC. */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/core/fsm.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/msc_i_remote.h>
#include <osmocom/msc/msc_roles.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_i.h>
#include <osmocom/msc/e_link.h>
static struct osmo_fsm msc_i_remote_fsm;
static struct msc_i *msc_i_remote_priv(struct osmo_fsm_inst *fi)
{
OSMO_ASSERT(fi);
OSMO_ASSERT(fi->fsm == &msc_i_remote_fsm);
OSMO_ASSERT(fi->priv);
return fi->priv;
}
/* The idea is that this msc_i role is event-compatible to the "real" msc_i.c FSM, but instead of acting on the events
* directly, it forwards the events to a remote MSC-I role, via E-over-GSUP.
*
* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
* you are here^
*/
static int msc_i_remote_msg_down_to_remote_msc(struct msc_i *msc_i,
enum osmo_gsup_message_type message_type,
struct an_apdu *an_apdu)
{
struct osmo_gsup_message m;
struct e_link *e = msc_i->c.remote_to;
if (!e) {
LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "No E link to remote MSC, cannot send AN-APDU\n");
return -1;
}
if (e_prep_gsup_msg(e, &m)) {
LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Error composing E-interface GSUP message\n");
return -1;
}
m.message_type = message_type;
if (an_apdu) {
if (gsup_msg_assign_an_apdu(&m, an_apdu)) {
LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Error composing E-interface GSUP message\n");
return -1;
}
}
return e_tx(e, &m);
}
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a <-- msc_i_remote <---GSUP---- msc_a_remote <-- msc_i <--BSSMAP--- [BSS]
* you are here^
*/
static int msc_i_remote_rx_gsup(struct msc_i *msc_i, const struct osmo_gsup_message *gsup_msg)
{
uint32_t event;
struct an_apdu an_apdu;
int rc;
/* MSC_A_EV_FROM_I_COMPLETE_LAYER_3 will never occur with a remote MSC-I, since all Complete Layer 3 will happen
* between a local MSC-A and local MSC-I roles. Only after an inter-MSC Handover will there possibly exist a
* remote MSC-I, which is long after Complete Layer 3. */
switch (gsup_msg->message_type) {
case OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST:
case OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_REQUEST:
event = MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST;
break;
case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST:
event = MSC_A_EV_FROM_I_SEND_END_SIGNAL_REQUEST;
break;
case OSMO_GSUP_MSGT_E_CLOSE:
case OSMO_GSUP_MSGT_E_ABORT:
case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
msc_i_clear(msc_i);
return 0;
default:
LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
return -1;
};
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a <-- msc_i_remote <---GSUP---- msc_a_remote <-- msc_i <--BSSMAP--- [BSS]
* ^you are here
*/
gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
rc = msub_role_dispatch(msc_i->c.msub, MSC_ROLE_A, event, &an_apdu);
if (an_apdu.msg)
msgb_free(an_apdu.msg);
return rc;
}
static void msc_i_remote_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_i *msc_i = msc_i_remote_priv(fi);
struct an_apdu *an_apdu;
switch (event) {
case MSC_REMOTE_EV_RX_GSUP:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a <-- msc_i_remote <---GSUP---- msc_a_remote <-- msc_i <--BSSMAP--- [BSS]
* you are here^
*/
msc_i_remote_rx_gsup(msc_i, (const struct osmo_gsup_message*)data);
return;
case MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
* ^you are here
*/
an_apdu = data;
msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST, an_apdu);
return;
case MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
* ^you are here
*/
an_apdu = data;
msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_RESULT, an_apdu);
return;
case MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
* ^you are here
*/
an_apdu = data;
msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_PREPARE_SUBSEQUENT_HANDOVER_ERROR, an_apdu);
return;
case MSC_I_EV_FROM_A_SEND_END_SIGNAL_RESPONSE:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_i_remote ----GSUP---> msc_a_remote --> msc_i ---BSSMAP--> [BSS]
* ^you are here
*/
an_apdu = data;
msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_RESULT, an_apdu);
return;
default:
OSMO_ASSERT(false);
}
}
static void msc_i_remote_fsm_clearing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, fi);
}
static void msc_i_remote_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct msc_i *msc_i = msc_i_remote_priv(fi);
msc_i_remote_msg_down_to_remote_msc(msc_i, OSMO_GSUP_MSGT_E_CLOSE, NULL);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state msc_i_remote_fsm_states[] = {
[MSC_I_ST_READY] = {
.name = "READY",
.action = msc_i_remote_fsm_ready,
.in_event_mask = 0
| S(MSC_REMOTE_EV_RX_GSUP)
| S(MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
| S(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT)
| S(MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR)
,
.out_state_mask = 0
| S(MSC_I_ST_CLEARING)
,
},
[MSC_I_ST_CLEARING] = {
.name = "CLEARING",
.onenter = msc_i_remote_fsm_clearing_onenter,
},
};
static struct osmo_fsm msc_i_remote_fsm = {
.name = "msc_i_remote",
.states = msc_i_remote_fsm_states,
.num_states = ARRAY_SIZE(msc_i_remote_fsm_states),
.log_subsys = DMSC,
.event_names = msc_i_fsm_event_names,
.cleanup = msc_i_remote_fsm_cleanup,
};
static __attribute__((constructor)) void msc_i_remote_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&msc_i_remote_fsm) == 0);
}
struct msc_i *msc_i_remote_alloc(struct msub *msub, struct ran_infra *ran, struct e_link *e)
{
struct msc_i *msc_i;
msub_role_alloc(msub, MSC_ROLE_I, &msc_i_remote_fsm, struct msc_i, ran);
msc_i = msub_msc_i(msub);
if (!msc_i)
return NULL;
e_link_assign(e, msc_i->c.fi);
if (!msc_i->c.remote_to) {
LOG_MSC_I_REMOTE(msc_i, LOGL_ERROR, "Failed to set up E link over GSUP to remote MSC\n");
msc_i_clear(msc_i);
return NULL;
}
return msc_i;
}

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

126
src/libmsc/msc_net_init.c Normal file
View File

@ -0,0 +1,126 @@
/* main MSC management code... */
/*
* (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
* (C) 2010 by On-Waves
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "bscconfig.h"
#include <osmocom/core/tdef.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/gsup_client_mux.h>
#include <osmocom/msc/gsm_04_11_gsup.h>
#include <osmocom/msc/gsm_09_11.h>
struct osmo_tdef mncc_tdefs[] = {
{}
};
struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv)
{
struct gsm_network *net;
net = talloc_zero(ctx, struct gsm_network);
if (!net)
return NULL;
net->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
/* Permit a compile-time default of A5/3 and A5/1 */
net->a5_encryption_mask = (1 << 3) | (1 << 1);
/* Use 30 min periodic update interval as sane default */
net->t3212 = 5;
net->mncc_guard_timeout = 180;
net->ncss_guard_timeout = 30;
net->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT;
INIT_LLIST_HEAD(&net->trans_list);
INIT_LLIST_HEAD(&net->upqueue);
INIT_LLIST_HEAD(&net->neighbor_ident_list);
/* init statistics */
net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
if (!net->msc_ctrs) {
talloc_free(net);
return NULL;
}
net->active_calls = osmo_counter_alloc("msc.active_calls");
net->active_nc_ss = osmo_counter_alloc("msc.active_nc_ss");
net->mncc_tdefs = mncc_tdefs;
net->mncc_recv = mncc_recv;
return net;
}
void gsm_network_set_mncc_sock_path(struct gsm_network *net, const char *mncc_sock_path)
{
if (net->mncc_sock_path)
talloc_free(net->mncc_sock_path);
net->mncc_sock_path = mncc_sock_path ? talloc_strdup(net, mncc_sock_path) : NULL;
}
/* Allocate net->vlr so that the VTY may configure the VLR's data structures */
int msc_vlr_alloc(struct gsm_network *net)
{
net->vlr = vlr_alloc(net, &msc_vlr_ops);
if (!net->vlr)
return -ENOMEM;
net->vlr->user_ctx = net;
return 0;
}
/* Launch the VLR, i.e. its GSUP connection */
int msc_vlr_start(struct gsm_network *net)
{
OSMO_ASSERT(net->vlr);
OSMO_ASSERT(net->gcm);
return vlr_start(net->vlr, net->gcm);
}
int msc_gsup_client_start(struct gsm_network *net)
{
struct ipaccess_unit *ipa_dev;
net->gcm = gsup_client_mux_alloc(net);
OSMO_ASSERT(net->gcm);
ipa_dev = talloc_zero(net->gcm, struct ipaccess_unit);
ipa_dev->unit_name = "MSC";
ipa_dev->serno = net->msc_ipa_name; /* NULL unless configured via VTY */
ipa_dev->swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
*net->gcm = (struct gsup_client_mux){
.rx_cb = {
/* vlr.c sets up its own cb and data */
/* MSC-A and MSC-B set up their own cb and data */
[OSMO_GSUP_MESSAGE_CLASS_SMS] = { .func = gsm411_gsup_rx, .data = net->vlr },
[OSMO_GSUP_MESSAGE_CLASS_USSD] = { .func = gsm0911_gsup_rx, .data = net->vlr },
},
};
return gsup_client_mux_start(net->gcm, net->gsup_server_addr_str, net->gsup_server_port, ipa_dev);
}

962
src/libmsc/msc_t.c Normal file
View File

@ -0,0 +1,962 @@
/* The MSC-T role, a transitional RAN connection during Handover. */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <inttypes.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/msc/msc_t.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/msc_a_remote.h>
#include <osmocom/msc/ran_infra.h>
#include <osmocom/msc/ran_peer.h>
#include <osmocom/msc/ran_conn.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/call_leg.h>
#include <osmocom/msc/rtp_stream.h>
#include <osmocom/msc/ran_infra.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/msc_i.h>
#include <osmocom/msc/gsm_data.h>
static struct osmo_fsm msc_t_fsm;
static struct msc_t *msc_t_find_by_handover_number(const char *handover_number)
{
struct msub *msub;
llist_for_each_entry(msub, &msub_list, entry) {
struct msc_t *msc_t = msub_msc_t(msub);
if (!msc_t)
continue;
if (!*msc_t->inter_msc.handover_number)
continue;
if (strcmp(msc_t->inter_msc.handover_number, handover_number))
continue;
/* Found the assigned Handover Number */
return msc_t;
}
return NULL;
}
static uint64_t net_handover_number_next(struct gsm_network *net)
{
uint64_t nr;
if (net->handover_number.next < net->handover_number.range_start
|| net->handover_number.next > net->handover_number.range_end)
net->handover_number.next = net->handover_number.range_start;
nr = net->handover_number.next;
net->handover_number.next++;
return nr;
}
static int msc_t_assign_handover_number(struct msc_t *msc_t)
{
int rc;
uint64_t started_at;
uint64_t ho_nr;
char ho_nr_str[VLR_MSISDN_LENGTH+1];
struct gsm_network *net = msc_t_net(msc_t);
bool usable = false;
started_at = ho_nr = net_handover_number_next(net);
if (!ho_nr) {
LOG_MSC_T(msc_t, LOGL_ERROR, "No Handover Number range defined in MSC config\n");
return -ENOENT;
}
do {
rc = snprintf(ho_nr_str, sizeof(ho_nr_str), "%"PRIu64, ho_nr);
if (rc <= 0 || rc >= sizeof(ho_nr_str)) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Cannot compose Handover Number string (rc=%d)\n", rc);
return -EINVAL;
}
if (!msc_t_find_by_handover_number(ho_nr_str)) {
usable = true;
break;
}
ho_nr = net_handover_number_next(net);
} while(ho_nr != started_at);
if (!usable) {
LOG_MSC_T(msc_t, LOGL_ERROR, "No Handover Number available\n");
return -EINVAL;
}
LOG_MSC_T(msc_t, LOGL_INFO, "Assigning Handover Number %s\n", ho_nr_str);
OSMO_STRLCPY_ARRAY(msc_t->inter_msc.handover_number, ho_nr_str);
return 0;
}
static struct msc_t *msc_t_priv(struct osmo_fsm_inst *fi)
{
OSMO_ASSERT(fi);
OSMO_ASSERT(fi->fsm == &msc_t_fsm);
OSMO_ASSERT(fi->priv);
return fi->priv;
}
/* As a macro to log the caller's source file and line.
* Assumes presence of local msc_t variable. */
#define msc_t_error(fmt, args...) do { \
msc_t->ho_success = false; \
LOG_MSC_T(msc_t, LOGL_ERROR, fmt, ##args); \
msc_t_clear(msc_t); \
} while(0)
static void msc_t_send_handover_failure(struct msc_t *msc_t, enum gsm0808_cause cause)
{
struct ran_msg ran_enc_msg = {
.msg_type = RAN_MSG_HANDOVER_FAILURE,
.handover_failure = {
.cause = cause,
},
};
struct an_apdu an_apdu = {
.an_proto = msc_t->c.ran->an_proto,
.msg = msc_role_ran_encode(msc_t->c.fi, &ran_enc_msg),
};
msc_t->ho_fail_sent = true;
if (!an_apdu.msg)
return;
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, &an_apdu);
msgb_free(an_apdu.msg);
}
static int msc_t_ho_request_decode_and_store_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
const struct ran_msg *ran_dec)
{
struct msc_t *msc_t = msc_t_priv(msc_t_fi);
if (ran_dec->msg_type != RAN_MSG_HANDOVER_REQUEST) {
LOG_MSC_T(msc_t, LOGL_DEBUG, "Expected %s in incoming inter-MSC Handover message, got %s\n",
ran_msg_type_name(RAN_MSG_HANDOVER_REQUEST), ran_msg_type_name(ran_dec->msg_type));
return -EINVAL;
}
msc_t->inter_msc.cell_id_target = ran_dec->handover_request.cell_id_target;
msc_t->inter_msc.callref = ran_dec->handover_request.call_id;
/* TODO other parameters...?
* Global Call Reference
*/
return 0;
}
/* On an icoming Handover Request from a remote MSC, we first need to set up an MGW endpoint, because the BSC needs to
* know our AoIP Transport Layer Address in the Handover Request message (which obviously the remote MSC doesn't send,
* it needs to be our local RTP address). Creating the MGW endpoint this is asynchronous, so we need to store the
* Handover Request data to forward to the BSC once the MGW endpoint is known.
*/
static int msc_t_decode_and_store_ho_request(struct msc_t *msc_t, const struct an_apdu *an_apdu)
{
if (msc_role_ran_decode(msc_t->c.fi, an_apdu, msc_t_ho_request_decode_and_store_cb, NULL)) {
msc_t_error("Failed to decode Handover Request\n");
return -ENOTSUP;
}
/* Ok, decoding done, and above msc_t_ho_request_decode_and_store_cb() has retrieved what info we need at this
* point and stored it in msc_t->inter_msc.* */
/* We're storing this for use after async events, so need to make sure that each and every bit of data is copied
* and no longer references some msgb that might be deallocated when this returns, nor remains in a local stack
* variable of some ran_decode implementation. The simplest is to store the entire msgb. */
msc_t->inter_msc.ho_request = (struct an_apdu) {
.an_proto = an_apdu->an_proto,
.msg = msgb_copy(an_apdu->msg, "saved inter-MSC Handover Request"),
/* A decoded osmo_gsup_message often still references memory of within the msgb the GSUP was received
* in. So, any info from an_apdu->e_info that would be needed would have to be copied separately.
* Omit e_info completely. */
};
return 0;
}
/* On an incoming Handover Request from a remote MSC, the target cell was transmitted in the Handover Request message.
* Find the RAN peer and assign from the cell id decoded above in msc_t_decode_and_store_ho_request(). */
static int msc_t_find_ran_peer_from_ho_request(struct msc_t *msc_t)
{
struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
const struct neighbor_ident_entry *nie;
struct ran_peer *rp_from_neighbor_ident;
struct ran_peer *rp;
switch (msc_ho_find_target_cell(msc_a, &msc_t->inter_msc.cell_id_target,
&nie, &rp_from_neighbor_ident, &rp)) {
case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
msc_t_error("Incoming Handover Request indicated target cell that belongs to a remote MSC:"
" Cell ID: %s; remote MSC: %s\n",
gsm0808_cell_id_name(&msc_t->inter_msc.cell_id_target),
neighbor_ident_addr_name(&nie->addr));
return -EINVAL;
case MSC_NEIGHBOR_TYPE_NONE:
msc_t_error("Incoming Handover Request for unknown cell %s\n",
gsm0808_cell_id_name(&msc_t->inter_msc.cell_id_target));
return -EINVAL;
case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
/* That's what is expected: a local RAN peer, e.g. BSC, or a remote BSC from neighbor cfg. */
if (!rp)
rp = rp_from_neighbor_ident;
break;
}
OSMO_ASSERT(rp);
LOG_MSC_T(msc_t, LOGL_DEBUG, "Incoming Handover Request indicates target cell %s,"
" which belongs to RAN peer %s\n",
gsm0808_cell_id_name(&msc_t->inter_msc.cell_id_target), rp->fi->id);
/* Finally we know where to direct the Handover */
msc_t_set_ran_peer(msc_t, rp);
return 0;
}
static int msc_t_send_stored_ho_request__decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
const struct ran_msg *ran_dec)
{
int rc;
struct an_apdu an_apdu;
struct msc_t *msc_t = msc_t_priv(msc_t_fi);
struct osmo_sockaddr_str *rtp_ran_local = data;
/* Copy ran_dec message to un-const so we can add the AoIP Transport Layer Address. All pointer references still
* remain on the same memory as ran_dec, which is fine. We're just going to encode it again right away. */
struct ran_msg ran_enc = *ran_dec;
if (ran_dec->msg_type != RAN_MSG_HANDOVER_REQUEST) {
LOG_MSC_T(msc_t, LOGL_DEBUG, "Expected %s in incoming inter-MSC Handover message, got %s\n",
ran_msg_type_name(RAN_MSG_HANDOVER_REQUEST), ran_msg_type_name(ran_dec->msg_type));
return -EINVAL;
}
/* Insert AoIP Transport Layer Address */
ran_enc.handover_request.rtp_ran_local = rtp_ran_local;
/* Finally ready to forward to BSC: encode and send out. */
an_apdu = (struct an_apdu){
.an_proto = msc_t->inter_msc.ho_request.an_proto,
.msg = msc_role_ran_encode(msc_t->c.fi, &ran_enc),
};
if (!an_apdu.msg)
return -EIO;
rc = msc_t_down_l2_co(msc_t, &an_apdu, true);
msgb_free(an_apdu.msg);
return rc;
}
/* The MGW endpoint is created, we know our AoIP Transport Layer Address and can send the Handover Request to the RAN
* peer. */
static int msc_t_send_stored_ho_request(struct msc_t *msc_t)
{
struct osmo_sockaddr_str *rtp_ran_local = call_leg_local_ip(msc_t->inter_msc.call_leg, RTP_TO_RAN);
if (!rtp_ran_local) {
msc_t_error("Local RTP address towards RAN is not set up properly, cannot send Handover Request\n");
return -EINVAL;
}
/* The Handover Request received from the remote MSC is fed through, except we need to insert our local AoIP
* Transport Layer Address, i.e. the RTP IP:port of the MGW towards the RAN side. So we actually need to decode,
* add the AoIP and re-encode. By nature of decoding, it goes through the decode callback. */
return msc_role_ran_decode(msc_t->c.fi, &msc_t->inter_msc.ho_request,
msc_t_send_stored_ho_request__decode_cb, rtp_ran_local);
}
static void msc_t_fsm_pending_first_co_initial_msg(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_t *msc_t = msc_t_priv(fi);
struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
struct an_apdu *an_apdu;
OSMO_ASSERT(msc_a);
switch (event) {
case MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST:
/* For an inter-MSC Handover coming in from a remote MSC, we do not yet know the RAN peer and AoIP
* Transport Layer Address.
* - RAN peer is found by decoding the actual Handover Request message and looking for the Cell
* Identifier (Target).
* - To be able to tell the BSC about an AoIP Transport Layer Address, we first need to create an MGW
* endpoint.
* For mere inter-BSC Handover, we know all of the above already. Find out which one this is.
*/
an_apdu = data;
if (!msc_a->c.remote_to) {
/* Inter-BSC */
osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_REQUEST_ACK, 0, 0);
/* Inter-BSC. All should be set up, just forward the message. */
if (msc_t_down_l2_co(msc_t, an_apdu, true))
msc_t_error("Failed to send AN-APDU to RAN peer\n");
} else {
/* Inter-MSC */
if (msc_t->ran_conn) {
msc_t_error("Unexpected state for inter-MSC Handover: RAN peer is already set up\n");
return;
}
if (msc_t_decode_and_store_ho_request(msc_t, an_apdu))
return;
if (msc_t_find_ran_peer_from_ho_request(msc_t))
return;
/* Relying on timeout of the MGW operations, see onenter() for this state. */
osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_LOCAL_RTP, 0, 0);
}
return;
case MSC_T_EV_CN_CLOSE:
msc_t_clear(msc_t);
return;
default:
OSMO_ASSERT(false);
}
}
void msc_t_fsm_wait_local_rtp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct msc_t *msc_t = msc_t_priv(fi);
struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
/* This only happens on inter-MSC HO incoming from a remote MSC */
if (!msc_a->c.remote_to) {
msc_t_error("Unexpected state: this is not an inter-MSC Handover\n");
return;
}
if (msc_t->inter_msc.call_leg) {
msc_t_error("Unexpected state: call leg already set up\n");
return;
}
msc_t->inter_msc.call_leg = call_leg_alloc(msc_t->c.fi,
MSC_EV_CALL_LEG_TERM,
MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
MSC_EV_CALL_LEG_RTP_COMPLETE,
MSC_EV_CALL_LEG_RTP_RELEASED);
if (!msc_t->inter_msc.call_leg
|| call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_RAN, msc_t->inter_msc.callref, NULL, NULL, NULL)
|| call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_CN, msc_t->inter_msc.callref, NULL, NULL, NULL)) {
msc_t_error("Failed to set up call leg\n");
return;
}
/* Now wait for two MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, one per RTP connection */
}
void msc_t_fsm_wait_local_rtp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_t *msc_t = msc_t_priv(fi);
struct rtp_stream *rtps;
switch (event) {
case MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE:
rtps = data;
if (!rtps) {
msc_t_error("Invalid data for MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE\n");
return;
}
/* If both to-RAN and to-CN sides have a CI set up, we can continue. */
if (!call_leg_local_ip(msc_t->inter_msc.call_leg, RTP_TO_RAN)
|| !call_leg_local_ip(msc_t->inter_msc.call_leg, RTP_TO_CN))
return;
osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_REQUEST_ACK, 0, 0);
msc_t_send_stored_ho_request(msc_t);
return;
case MSC_EV_CALL_LEG_RTP_RELEASED:
case MSC_EV_CALL_LEG_TERM:
msc_t->inter_msc.call_leg = NULL;
msc_t_error("Failed to set up MGW endpoint\n");
return;
case MSC_MNCC_EV_CALL_ENDED:
msc_t->inter_msc.mncc_forwarding_to_remote_cn = NULL;
return;
case MSC_T_EV_CN_CLOSE:
case MSC_T_EV_MO_CLOSE:
msc_t_clear(msc_t);
return;
default:
OSMO_ASSERT(false);
}
}
static int msc_t_patch_and_send_ho_request_ack(struct msc_t *msc_t, const struct an_apdu *incoming_an_apdu,
const struct ran_msg *ran_dec)
{
int rc;
struct rtp_stream *rtp_ran = msc_t->inter_msc.call_leg? msc_t->inter_msc.call_leg->rtp[RTP_TO_RAN] : NULL;
struct rtp_stream *rtp_cn = msc_t->inter_msc.call_leg? msc_t->inter_msc.call_leg->rtp[RTP_TO_CN] : NULL;
/* Since it's BCD, it needs rounded-up half the char* length of an MSISDN plus a type byte.
* But no need to introduce obscure math to save a few stack bytes, just have more. */
uint8_t msisdn_enc_buf[VLR_MSISDN_LENGTH + 1];
/* Copy an_apdu and an_apdu->e_info in "copy-on-write" method, because they are const and we
* need to add the Handover Number to e_info. */
const struct ran_handover_request_ack *r = &ran_dec->handover_request_ack;
struct ran_msg ran_enc = *ran_dec;
struct osmo_gsup_message e_info = {};
struct an_apdu an_apdu = {
.an_proto = incoming_an_apdu->an_proto,
.e_info = &e_info,
};
if (incoming_an_apdu->e_info)
e_info = *incoming_an_apdu->e_info;
rc = msc_t_assign_handover_number(msc_t);
if (rc)
return rc;
rc = gsm48_encode_bcd_number(msisdn_enc_buf, sizeof(msisdn_enc_buf), 0,
msc_t->inter_msc.handover_number);
if (rc <= 0)
return -EINVAL;
e_info.msisdn_enc = msisdn_enc_buf;
e_info.msisdn_enc_len = rc;
/* Also need to fetch the RTP IP:port from AoIP Transport Address IE to tell the MGW about it */
if (rtp_ran) {
if (osmo_sockaddr_str_is_set(&r->remote_rtp)) {
LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&r->remote_rtp));
rtp_stream_set_remote_addr(rtp_ran, &r->remote_rtp);
} else {
LOG_MSC_T(msc_t, LOGL_DEBUG, "No RTP IP:port in Handover Request Ack\n");
}
if (r->codec_present) {
LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got %s\n",
osmo_mgcpc_codec_name(r->codec));
rtp_stream_set_codec(rtp_ran, r->codec);
if (rtp_cn)
rtp_stream_set_codec(rtp_cn, r->codec);
} else {
LOG_MSC_T(msc_t, LOGL_DEBUG, "No codec in Handover Request Ack\n");
}
rtp_stream_commit(rtp_ran);
} else {
LOG_MSC_T(msc_t, LOGL_DEBUG, "No RTP to RAN set up yet\n");
}
/* Remove that AoIP Transport Layer IE so it doesn't get sent to the remote MSC */
ran_enc.handover_request_ack.remote_rtp = (struct osmo_sockaddr_str){};
an_apdu.msg = msc_role_ran_encode(msc_t->c.fi, &ran_enc);
if (!an_apdu.msg)
return -EIO;
/* Send to remote MSC via msc_a_remote role */
rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE, &an_apdu);
msgb_free(an_apdu.msg);
return rc;
}
static int msc_t_wait_ho_request_ack_decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
const struct ran_msg *ran_dec)
{
int rc;
struct msc_t *msc_t = msc_t_priv(msc_t_fi);
struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
const struct an_apdu *an_apdu = data;
switch (ran_dec->msg_type) {
case RAN_MSG_HANDOVER_REQUEST_ACK:
if (msc_a->c.remote_to) {
/* inter-MSC. Add Handover Number, remove AoIP Transport Layer Address. */
rc = msc_t_patch_and_send_ho_request_ack(msc_t, an_apdu, ran_dec);
} else {
/* inter-BSC. Just send as-is, with correct event. */
rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE,
an_apdu);
}
if (rc)
msc_t_error("Failed to send HO Request Ack\n");
else
osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_COMPLETE, 0, 0);
return 0;
case RAN_MSG_HANDOVER_FAILURE:
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, an_apdu);
return 0;
case RAN_MSG_CLEAR_REQUEST:
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
an_apdu);
return 0;
default:
LOG_MSC_T(msc_t, LOGL_ERROR, "Unexpected message during Prepare Handover procedure: %s\n",
ran_msg_type_name(ran_dec->msg_type));
/* Let's just forward anyway. */
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
an_apdu);
return 0;
}
}
static void msc_t_fsm_wait_ho_request_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_t *msc_t = msc_t_priv(fi);
struct an_apdu *an_apdu;
switch (event) {
case MSC_EV_FROM_RAN_UP_L2:
an_apdu = data;
/* For inter-MSC Handover, we need to examine the message type. Depending on the response, we must
* dispatch MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE or MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, which
* ensures the correct E-interface message type. And we need to include the Handover Number.
* For mere inter-BSC Handover, we know that our osmo-msc internals don't care much about which event
* dispatches a Handover Failure or Handover Request Ack, so we could skip the decoding. But it is a
* premature optimization that complicates comparing an inter-BSC with an inter-MSC HO. */
msc_role_ran_decode(msc_t->c.fi, an_apdu, msc_t_wait_ho_request_ack_decode_cb, an_apdu);
/* Action continues in msc_t_wait_ho_request_ack_decode_cb() */
return;
case MSC_EV_FROM_RAN_CONN_RELEASED:
msc_t_clear(msc_t);
return;
case MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
an_apdu = data;
msc_t_down_l2_co(msc_t, an_apdu, false);
return;
case MSC_EV_CALL_LEG_RTP_RELEASED:
case MSC_EV_CALL_LEG_TERM:
msc_t->inter_msc.call_leg = NULL;
msc_t_error("Failed to set up MGW endpoint\n");
return;
case MSC_MNCC_EV_CALL_ENDED:
msc_t->inter_msc.mncc_forwarding_to_remote_cn = NULL;
return;
case MSC_T_EV_CN_CLOSE:
case MSC_T_EV_MO_CLOSE:
msc_t_clear(msc_t);
return;
default:
OSMO_ASSERT(false);
}
}
static int msc_t_wait_ho_complete_decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data,
const struct ran_msg *ran_dec)
{
struct msc_t *msc_t = msc_t_priv(msc_t_fi);
struct msc_a *msc_a = msub_msc_a(msc_t->c.msub);
struct msc_i *msc_i;
const struct an_apdu *an_apdu = data;
switch (ran_dec->msg_type) {
case RAN_MSG_HANDOVER_COMPLETE:
msc_t->ho_success = true;
/* For both inter-BSC local to this MSC and inter-MSC Handover for a remote MSC-A, forward the Handover
* Complete message so that the MSC-A can change the MSC-T (transitional) to a proper MSC-I role. */
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST, an_apdu);
/* For inter-BSC Handover, the Handover Complete event has already cleaned up this msc_t, and it is
* already gone and deallocated. */
if (!msc_a->c.remote_to)
return 0;
/* For inter-MSC Handover, the remote MSC-A only turns its msc_t_remote into an msc_i_remote on
* the same GSUP link. We are here on the MSC-B side of the GSUP link and have to take care of
* creating an MSC-I over here to match the msc_i_remote at MSC-A. */
msc_i = msc_i_alloc(msc_t->c.msub, msc_t->c.ran);
if (!msc_i) {
msc_t_error("Failed to create MSC-I role\n");
return -1;
}
msc_i->inter_msc.mncc_forwarding_to_remote_cn = msc_t->inter_msc.mncc_forwarding_to_remote_cn;
mncc_call_reparent(msc_i->inter_msc.mncc_forwarding_to_remote_cn,
msc_i->c.fi, -1, MSC_MNCC_EV_CALL_ENDED, NULL, NULL);
msc_i->inter_msc.call_leg = msc_t->inter_msc.call_leg;
call_leg_reparent(msc_i->inter_msc.call_leg,
msc_i->c.fi,
MSC_EV_CALL_LEG_TERM,
MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
MSC_EV_CALL_LEG_RTP_COMPLETE,
MSC_EV_CALL_LEG_RTP_RELEASED);
/* msc_i_set_ran_conn() properly "steals" the ran_conn from msc_t */
msc_i_set_ran_conn(msc_i, msc_t->ran_conn);
/* Nicked everything worth keeping from MSC-T, discard now. */
msc_t_clear(msc_t);
return 0;
case RAN_MSG_HANDOVER_FAILURE:
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, an_apdu);
return 0;
default:
LOG_MSC_T(msc_t, LOGL_ERROR, "Unexpected message during Prepare Handover procedure: %s\n",
ran_msg_type_name(ran_dec->msg_type));
/* Let's just forward anyway. Fall thru */
case RAN_MSG_HANDOVER_DETECT:
case RAN_MSG_CLEAR_REQUEST:
msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST,
an_apdu);
return 0;
}
}
static void msc_t_fsm_wait_ho_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_t *msc_t = msc_t_priv(fi);
struct an_apdu *an_apdu;
switch (event) {
case MSC_EV_FROM_RAN_UP_L2:
an_apdu = data;
/* We need to catch the Handover Complete message in order to send it as a SendEndSignal Request */
msc_role_ran_decode(msc_t->c.fi, an_apdu, msc_t_wait_ho_complete_decode_cb, an_apdu);
return;
case MSC_EV_FROM_RAN_CONN_RELEASED:
msc_t_clear(msc_t);
return;
case MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
an_apdu = data;
msc_t_down_l2_co(msc_t, an_apdu, false);
return;
case MSC_EV_CALL_LEG_RTP_RELEASED:
case MSC_EV_CALL_LEG_TERM:
msc_t->inter_msc.call_leg = NULL;
msc_t_error("Failed to set up MGW endpoint\n");
return;
case MSC_MNCC_EV_CALL_ENDED:
msc_t->inter_msc.mncc_forwarding_to_remote_cn = NULL;
return;
case MSC_T_EV_CN_CLOSE:
case MSC_T_EV_MO_CLOSE:
msc_t_clear(msc_t);
return;
default:
OSMO_ASSERT(false);
}
}
void msc_t_mncc_cb(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg, void *data)
{
struct msc_t *msc_t = data;
struct gsm_mncc_number nr = {
.plan = 1,
};
OSMO_STRLCPY_ARRAY(nr.number, msc_t->inter_msc.handover_number);
switch (mncc_msg->msg_type) {
case MNCC_RTP_CREATE:
mncc_call_incoming_tx_setup_cnf(mncc_call, &nr);
return;
default:
return;
}
}
struct mncc_call *msc_t_check_call_to_handover_number(const struct gsm_mncc *msg)
{
struct msc_t *msc_t;
const char *handover_number;
struct mncc_call_incoming_req req;
struct mncc_call *mncc_call;
if (!(msg->fields & MNCC_F_CALLED))
return NULL;
handover_number = msg->called.number;
msc_t = msc_t_find_by_handover_number(handover_number);
if (!msc_t)
return NULL;
if (msc_t->inter_msc.mncc_forwarding_to_remote_cn) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Incoming call for inter-MSC call forwarding,"
" but this MSC-T role already has an MNCC FSM set up\n");
return NULL;
}
if (!msc_t->inter_msc.call_leg
|| !msc_t->inter_msc.call_leg->rtp[RTP_TO_CN]) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Incoming call for inter-MSC call forwarding,"
" but this MSC-T has no RTP stream ready for MNCC\n");
return NULL;
}
mncc_call = mncc_call_alloc(msc_t_vsub(msc_t),
msc_t->c.fi,
MSC_MNCC_EV_CALL_COMPLETE,
MSC_MNCC_EV_CALL_ENDED,
msc_t_mncc_cb, msc_t);
if (!mncc_call) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to set up call forwarding from remote MSC\n");
return NULL;
}
msc_t->inter_msc.mncc_forwarding_to_remote_cn = mncc_call;
if (mncc_call_set_rtp_stream(mncc_call, msc_t->inter_msc.call_leg->rtp[RTP_TO_CN])) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to set up call forwarding from remote MSC\n");
osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, NULL);
return NULL;
}
req = (struct mncc_call_incoming_req){
.setup_req_msg = *msg,
.bearer_cap_present = true,
.bearer_cap = {
/* TODO derive values from actual config */
/* FIXME are there no defines or enums for these numbers!? */
/* Table 10.5.102/3GPP TS 24.008: Bearer capability information element:
* octet 3 of bearer cap for speech says 3 = "1 1 dual rate support MS/full rate speech version
* 1 preferred, half rate speech version 1 also supported" */
.radio = 3,
/* Table 10.5.103/3GPP TS 24.008 Bearer capability information element:
* 0: FR1, 2: FR2, 4: FR3, 1: HR1, 5: HR3, actually in this order. -1 marks the end of the list. */
.speech_ver = { 0, 2, 4, 1, 5, -1 },
},
};
if (mncc_call_incoming_start(mncc_call, &req)) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to set up call forwarding from remote MSC\n");
osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, NULL);
return NULL;
}
return mncc_call;
}
static void msc_t_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct msc_t *msc_t = msc_t_priv(fi);
if (!msc_t->ho_success && !msc_t->ho_fail_sent)
msc_t_send_handover_failure(msc_t, GSM0808_CAUSE_EQUIPMENT_FAILURE);
if (msc_t->ran_conn)
ran_conn_msc_role_gone(msc_t->ran_conn, msc_t->c.fi);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state msc_t_fsm_states[] = {
[MSC_T_ST_PENDING_FIRST_CO_INITIAL_MSG] = {
.name = "PENDING_FIRST_CO_INITIAL_MSG",
.action = msc_t_fsm_pending_first_co_initial_msg,
.in_event_mask = 0
| S(MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST)
| S(MSC_T_EV_CN_CLOSE)
,
.out_state_mask = 0
| S(MSC_T_ST_WAIT_LOCAL_RTP)
| S(MSC_T_ST_WAIT_HO_REQUEST_ACK)
,
},
[MSC_T_ST_WAIT_LOCAL_RTP] = {
.name = "WAIT_LOCAL_RTP",
.onenter = msc_t_fsm_wait_local_rtp_onenter,
.action = msc_t_fsm_wait_local_rtp,
.in_event_mask = 0
| S(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE)
| S(MSC_EV_CALL_LEG_RTP_RELEASED)
| S(MSC_EV_CALL_LEG_TERM)
| S(MSC_MNCC_EV_CALL_ENDED)
| S(MSC_T_EV_CN_CLOSE)
,
.out_state_mask = 0
| S(MSC_T_ST_WAIT_HO_REQUEST_ACK)
,
},
[MSC_T_ST_WAIT_HO_REQUEST_ACK] = {
.name = "WAIT_HO_REQUEST_ACK",
.action = msc_t_fsm_wait_ho_request_ack,
.in_event_mask = 0
| S(MSC_EV_FROM_RAN_UP_L2)
| S(MSC_EV_FROM_RAN_CONN_RELEASED)
| S(MSC_EV_CALL_LEG_RTP_RELEASED)
| S(MSC_EV_CALL_LEG_TERM)
| S(MSC_MNCC_EV_CALL_ENDED)
| S(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
| S(MSC_T_EV_CN_CLOSE)
| S(MSC_T_EV_MO_CLOSE)
,
.out_state_mask = 0
| S(MSC_T_ST_WAIT_HO_COMPLETE)
,
},
[MSC_T_ST_WAIT_HO_COMPLETE] = {
.name = "WAIT_HO_COMPLETE",
.action = msc_t_fsm_wait_ho_complete,
.in_event_mask = 0
| S(MSC_EV_FROM_RAN_UP_L2)
| S(MSC_EV_FROM_RAN_CONN_RELEASED)
| S(MSC_EV_CALL_LEG_RTP_RELEASED)
| S(MSC_EV_CALL_LEG_TERM)
| S(MSC_MNCC_EV_CALL_ENDED)
| S(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
| S(MSC_T_EV_CN_CLOSE)
| S(MSC_T_EV_MO_CLOSE)
,
},
};
const struct value_string msc_t_fsm_event_names[] = {
OSMO_VALUE_STRING(MSC_REMOTE_EV_RX_GSUP),
OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE),
OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_COMPLETE),
OSMO_VALUE_STRING(MSC_EV_CALL_LEG_RTP_RELEASED),
OSMO_VALUE_STRING(MSC_EV_CALL_LEG_TERM),
OSMO_VALUE_STRING(MSC_MNCC_EV_NEED_LOCAL_RTP),
OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_PROCEEDING),
OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_COMPLETE),
OSMO_VALUE_STRING(MSC_MNCC_EV_CALL_ENDED),
OSMO_VALUE_STRING(MSC_EV_FROM_RAN_COMPLETE_LAYER_3),
OSMO_VALUE_STRING(MSC_EV_FROM_RAN_UP_L2),
OSMO_VALUE_STRING(MSC_EV_FROM_RAN_CONN_RELEASED),
OSMO_VALUE_STRING(MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST),
OSMO_VALUE_STRING(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST),
OSMO_VALUE_STRING(MSC_T_EV_CN_CLOSE),
OSMO_VALUE_STRING(MSC_T_EV_MO_CLOSE),
OSMO_VALUE_STRING(MSC_T_EV_CLEAR_COMPLETE),
{}
};
static struct osmo_fsm msc_t_fsm = {
.name = "msc_t",
.states = msc_t_fsm_states,
.num_states = ARRAY_SIZE(msc_t_fsm_states),
.log_subsys = DMSC,
.event_names = msc_t_fsm_event_names,
.cleanup = msc_t_fsm_cleanup,
};
static __attribute__((constructor)) void msc_t_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&msc_t_fsm) == 0);
}
/* Send connection-oriented L3 message to RAN peer (MSC->[BSC|RNC]) */
int msc_t_down_l2_co(struct msc_t *msc_t, const struct an_apdu *an_apdu, bool initial)
{
int rc;
if (!msc_t->ran_conn) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Cannot Tx L2 message: no RAN conn\n");
return -EIO;
}
if (an_apdu->an_proto != msc_t->c.ran->an_proto) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Mismatching AN-APDU proto: %s -- Dropping message\n",
an_proto_name(an_apdu->an_proto));
return -EIO;
}
rc = ran_conn_down_l2_co(msc_t->ran_conn, an_apdu->msg, initial);
if (rc)
LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to transfer message down to new RAN peer (rc=%d)\n", rc);
return rc;
}
struct gsm_network *msc_t_net(const struct msc_t *msc_t)
{
return msub_net(msc_t->c.msub);
}
struct vlr_subscr *msc_t_vsub(const struct msc_t *msc_t)
{
return msub_vsub(msc_t->c.msub);
}
struct msc_t *msc_t_alloc_without_ran_peer(struct msub *msub, struct ran_infra *ran)
{
struct msc_t *msc_t;
msub_role_alloc(msub, MSC_ROLE_T, &msc_t_fsm, struct msc_t, ran);
msc_t = msub_msc_t(msub);
if (!msc_t)
return NULL;
return msc_t;
}
int msc_t_set_ran_peer(struct msc_t *msc_t, struct ran_peer *ran_peer)
{
if (!ran_peer || !ran_peer->sri || !ran_peer->sri->ran) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Invalid RAN peer: %s\n", ran_peer ? ran_peer->fi->id : "NULL");
return -EINVAL;
}
if (ran_peer->sri->ran != msc_t->c.ran) {
LOG_MSC_T(msc_t, LOGL_ERROR, "This MSC-T was set up for %s, cannot assign RAN peer for %s\n",
osmo_rat_type_name(msc_t->c.ran->type), osmo_rat_type_name(ran_peer->sri->ran->type));
return -EINVAL;
}
/* Create a new ran_conn with a fresh conn_id for the outgoing initial message. The msc_t FSM definition ensures
* that the first message sent or received is a Connection-Oriented Initial message. */
msc_t->ran_conn = ran_conn_create_outgoing(ran_peer);
if (!msc_t->ran_conn) {
LOG_MSC_T(msc_t, LOGL_ERROR, "Failed to create outgoing RAN conn\n");
return -EINVAL;
}
msc_t->ran_conn->msc_role = msc_t->c.fi;
msub_update_id(msc_t->c.msub);
return 0;
}
struct msc_t *msc_t_alloc(struct msub *msub, struct ran_peer *ran_peer)
{
struct msc_t *msc_t = msc_t_alloc_without_ran_peer(msub, ran_peer->sri->ran);
if (!msc_t)
return NULL;
if (msc_t_set_ran_peer(msc_t, ran_peer)) {
msc_t_clear(msc_t);
return NULL;
}
return msc_t;
}
void msc_t_clear(struct msc_t *msc_t)
{
if (!msc_t)
return;
osmo_fsm_inst_term(msc_t->c.fi, OSMO_FSM_TERM_REGULAR, msc_t->c.fi);
}

226
src/libmsc/msc_t_remote.c Normal file
View File

@ -0,0 +1,226 @@
/* The MSC-T role implementation variant that forwards requests to/from a remote MSC. */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/core/fsm.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/msc_t_remote.h>
#include <osmocom/msc/msc_roles.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_t.h>
#include <osmocom/msc/e_link.h>
static struct osmo_fsm msc_t_remote_fsm;
static struct msc_t *msc_t_remote_priv(struct osmo_fsm_inst *fi)
{
OSMO_ASSERT(fi);
OSMO_ASSERT(fi->fsm == &msc_t_remote_fsm);
OSMO_ASSERT(fi->priv);
return fi->priv;
}
/* The idea is that this msc_t role is event-compatible to the "real" msc_t.c FSM, but instead of acting on the events
* directly, it forwards the events to a remote MSC-T role, via E-over-GSUP.
*
* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_t_remote ----GSUP---> msc_a_remote --> msc_t ---BSSMAP--> [BSS]
* you are here^
*/
static int msc_t_remote_msg_down_to_remote_msc(struct msc_t *msc_t,
enum osmo_gsup_message_type message_type,
struct an_apdu *an_apdu)
{
struct osmo_gsup_message m;
struct e_link *e = msc_t->c.remote_to;
if (!e) {
LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "No E link to remote MSC, cannot send AN-APDU\n");
return -1;
}
if (e_prep_gsup_msg(e, &m)) {
LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Error composing E-interface GSUP message\n");
return -1;
}
m.message_type = message_type;
if (an_apdu) {
if (gsup_msg_assign_an_apdu(&m, an_apdu)) {
LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Error composing E-interface GSUP message\n");
return -1;
}
}
return e_tx(e, &m);
}
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a <-- msc_t_remote <---GSUP---- msc_a_remote <-- msc_t <--BSSMAP--- [BSS]
* you are here^
*/
static int msc_t_remote_rx_gsup(struct msc_t *msc_t, const struct osmo_gsup_message *gsup_msg)
{
uint32_t event;
struct an_apdu an_apdu;
int rc;
switch (gsup_msg->message_type) {
case OSMO_GSUP_MSGT_E_PROCESS_ACCESS_SIGNALLING_REQUEST:
event = MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST;
break;
case OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR:
case OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_RESULT:
event = MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE;
break;
case OSMO_GSUP_MSGT_E_SEND_END_SIGNAL_REQUEST:
event = MSC_A_EV_FROM_T_SEND_END_SIGNAL_REQUEST;
break;
case OSMO_GSUP_MSGT_E_CLOSE:
case OSMO_GSUP_MSGT_E_ABORT:
case OSMO_GSUP_MSGT_E_ROUTING_ERROR:
msc_t_clear(msc_t);
return 0;
default:
LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Unhandled GSUP message type: %s\n",
osmo_gsup_message_type_name(gsup_msg->message_type));
return -1;
};
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a <-- msc_t_remote <---GSUP---- msc_a_remote <-- msc_t <--BSSMAP--- [BSS]
* ^you are here
*/
gsup_msg_to_an_apdu(&an_apdu, gsup_msg);
rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, event, &an_apdu);
if (an_apdu.msg)
msgb_free(an_apdu.msg);
return rc;
}
static void msc_t_remote_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msc_t *msc_t = msc_t_remote_priv(fi);
struct an_apdu *an_apdu;
switch (event) {
case MSC_REMOTE_EV_RX_GSUP:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a <-- msc_t_remote <---GSUP---- msc_a_remote <-- msc_t <--BSSMAP--- [BSS]
* you are here^
*/
msc_t_remote_rx_gsup(msc_t, (const struct osmo_gsup_message*)data);
return;
case MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_t_remote ----GSUP---> going to create an msc_t if the request succeeds
* ^you are here
*/
an_apdu = data;
msc_t_remote_msg_down_to_remote_msc(msc_t, OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST, an_apdu);
return;
case MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST:
/* [MSC-A-----------------] [MSC-B-----------------]
* msc_a --> msc_t_remote ----GSUP---> msc_a_remote --> msc_t ---BSSMAP--> [BSS]
* ^you are here
*/
an_apdu = data;
msc_t_remote_msg_down_to_remote_msc(msc_t, OSMO_GSUP_MSGT_E_FORWARD_ACCESS_SIGNALLING_REQUEST, an_apdu);
return;
default:
OSMO_ASSERT(false);
}
}
static void msc_t_remote_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct msc_t *msc_t = msc_t_remote_priv(fi);
if (msc_t->c.remote_to)
msc_t_remote_msg_down_to_remote_msc(msc_t, OSMO_GSUP_MSGT_E_CLOSE, NULL);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state msc_t_remote_fsm_states[] = {
/* An FSM instance always starts in state 0. Define one just to be able to state_chg out of it. Root reason is
* that we're using MSC_T_ST_* enum values from msc_t.c, but don't need the first
* MSC_T_ST_PENDING_FIRST_CO_INITIAL_MSG. */
[0] = {
.name = "0",
.out_state_mask = 0
| S(MSC_T_ST_WAIT_HO_COMPLETE)
,
},
[MSC_T_ST_WAIT_HO_COMPLETE] = {
.name = "WAIT_HO_COMPLETE",
.action = msc_t_remote_fsm_ready,
.in_event_mask = 0
| S(MSC_REMOTE_EV_RX_GSUP)
| S(MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST)
| S(MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST)
,
},
};
static struct osmo_fsm msc_t_remote_fsm = {
.name = "msc_t_remote",
.states = msc_t_remote_fsm_states,
.num_states = ARRAY_SIZE(msc_t_remote_fsm_states),
.log_subsys = DMSC,
.event_names = msc_t_fsm_event_names,
.cleanup = msc_t_remote_fsm_cleanup,
};
static __attribute__((constructor)) void msc_t_remote_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&msc_t_remote_fsm) == 0);
}
struct msc_t *msc_t_remote_alloc(struct msub *msub, struct ran_infra *ran,
const uint8_t *remote_msc_name, size_t remote_msc_name_len)
{
struct msc_t *msc_t;
msub_role_alloc(msub, MSC_ROLE_T, &msc_t_remote_fsm, struct msc_t, ran);
msc_t = msub_msc_t(msub);
if (!msc_t)
return NULL;
msc_t->c.remote_to = e_link_alloc(msub_net(msub)->gcm, msc_t->c.fi, remote_msc_name, remote_msc_name_len);
if (!msc_t->c.remote_to) {
LOG_MSC_T_REMOTE(msc_t, LOGL_ERROR, "Failed to set up E link over GSUP to remote MSC\n");
msc_t_clear(msc_t);
return NULL;
}
osmo_fsm_inst_state_chg(msc_t->c.fi, MSC_T_ST_WAIT_HO_COMPLETE, 0, 0);
return msc_t;
}

View File

@ -28,6 +28,8 @@
#include <inttypes.h> #include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <osmocom/core/use_count.h>
#include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/gsm/protocol/gsm_04_14.h> #include <osmocom/gsm/protocol/gsm_04_14.h>
#include <osmocom/gsm/protocol/gsm_08_08.h> #include <osmocom/gsm/protocol/gsm_08_08.h>
@ -46,10 +48,11 @@
#include <osmocom/msc/vty.h> #include <osmocom/msc/vty.h>
#include <osmocom/msc/gsm_data.h> #include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsm_subscriber.h> #include <osmocom/msc/gsm_subscriber.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/vlr.h> #include <osmocom/msc/vlr.h>
#include <osmocom/msc/transaction.h> #include <osmocom/msc/transaction.h>
#include <osmocom/msc/db.h> #include <osmocom/msc/db.h>
#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/sms_queue.h> #include <osmocom/msc/sms_queue.h>
#include <osmocom/msc/silent_call.h> #include <osmocom/msc/silent_call.h>
#include <osmocom/msc/gsm_04_80.h> #include <osmocom/msc/gsm_04_80.h>
@ -59,6 +62,8 @@
#include <osmocom/msc/rrlp.h> #include <osmocom/msc/rrlp.h>
#include <osmocom/msc/vlr_sgs.h> #include <osmocom/msc/vlr_sgs.h>
#include <osmocom/msc/sgs_vty.h> #include <osmocom/msc/sgs_vty.h>
#include <osmocom/msc/sccp_ran.h>
#include <osmocom/msc/ran_peer.h>
static struct gsm_network *gsmnet = NULL; static struct gsm_network *gsmnet = NULL;
@ -504,6 +509,51 @@ DEFUN(cfg_msc_no_sms_over_gsup, cfg_msc_no_sms_over_gsup_cmd,
return CMD_SUCCESS; return CMD_SUCCESS;
} }
/* FIXME: This should rather be in the form of
* handover-number range 001234xxx
* and
* handover-number range 001234xxx FIRST LAST
*/
DEFUN(cfg_msc_handover_number_range, cfg_msc_handover_number_range_cmd,
"handover-number range MSISDN_FIRST MSISDN_LAST",
"Configure a range of MSISDN to be assigned to incoming inter-MSC Handovers for call forwarding.\n"
"Configure a handover number range\n"
"First Handover Number MSISDN\n"
"Last Handover Number MSISDN\n")
{
char *endp;
uint64_t range_start;
uint64_t range_end;
/* FIXME leading zeros?? */
errno = 0;
range_start = strtoull(argv[0], &endp, 10);
if (errno || *endp != '\0') {
vty_out(vty, "%% Error parsing handover-number range start: %s%s",
argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
errno = 0;
range_end = strtoull(argv[1], &endp, 10);
if (errno || *endp != '\0') {
vty_out(vty, "%% Error parsing handover-number range end: %s%s",
argv[1], VTY_NEWLINE);
return CMD_WARNING;
}
if (range_start > range_end) {
vty_out(vty, "%% Error: handover-number range end must be > than the range start, but"
" %"PRIu64" > %"PRIu64"%s", range_start, range_end, VTY_NEWLINE);
return CMD_WARNING;
}
gsmnet->handover_number.range_start = range_start;
gsmnet->handover_number.range_end = range_end;
return CMD_SUCCESS;
}
static int config_write_msc(struct vty *vty) static int config_write_msc(struct vty *vty)
{ {
vty_out(vty, "msc%s", VTY_NEWLINE); vty_out(vty, "msc%s", VTY_NEWLINE);
@ -546,6 +596,11 @@ static int config_write_msc(struct vty *vty)
if (gsmnet->sms_over_gsup) if (gsmnet->sms_over_gsup)
vty_out(vty, " sms-over-gsup%s", VTY_NEWLINE); vty_out(vty, " sms-over-gsup%s", VTY_NEWLINE);
if (gsmnet->handover_number.range_start || gsmnet->handover_number.range_end)
vty_out(vty, " handover-number range %"PRIu64" %"PRIu64"%s",
gsmnet->handover_number.range_start, gsmnet->handover_number.range_end,
VTY_NEWLINE);
mgcp_client_config_write(vty, " "); mgcp_client_config_write(vty, " ");
#ifdef BUILD_IU #ifdef BUILD_IU
ranap_iu_vty_config_write(vty, " "); ranap_iu_vty_config_write(vty, " ");
@ -557,81 +612,81 @@ static int config_write_msc(struct vty *vty)
DEFUN(show_bsc, show_bsc_cmd, DEFUN(show_bsc, show_bsc_cmd,
"show bsc", SHOW_STR "BSC\n") "show bsc", SHOW_STR "BSC\n")
{ {
struct bsc_context *bsc_ctx; struct ran_peer *rp;
struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(gsmnet->a.cs7_instance); llist_for_each_entry(rp, &gsmnet->a.sri->ran_peers, entry) {
vty_out(vty, "BSC %s %s%s",
llist_for_each_entry(bsc_ctx, &gsmnet->a.bscs, list) { osmo_sccp_inst_addr_name(gsmnet->a.sri->sccp, &rp->peer_addr),
vty_out(vty, "BSC %s%s", osmo_sccp_addr_name(ss7, &bsc_ctx->bsc_addr), VTY_NEWLINE); osmo_fsm_inst_state_name(rp->fi),
VTY_NEWLINE);
} }
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static void vty_conn_hdr(struct vty *vty) /*
_Subscriber_______________________________________ _LAC_ _RAN___________________ _MSC-A_state_________ _MSC-A_use_
IMSI-123456789012345:MSISDN-12345:TMSI-0x12345678 1 GERAN-A-4294967295:A5-3 WAIT_CLASSMARK_UPDATE 2=cm_service,trans_cc
IMSI-123456789012356:MSISDN-234567:TMSI-0x123ABC78 65535 UTRAN-Iu-4294967295 COMMUNICATING 2=cm_service,trans_sms
IMSI-123456789012367:MSISDN-98712345890:TMSI-0xF.. - EUTRAN-SGs RELEASING 0=none
IMSI-123456789012378:HONR-12345432101 2 MSC-901-700-423:9876 REMOTE_MSC_A 1=inter_msc
*/
static void vty_dump_one_conn(struct vty *vty, const struct msub *msub, int *idx)
{ {
unsigned lnum = 0; struct msc_a *msc_a = msub_msc_a(msub);
struct ran_conn *conn; struct vlr_subscr *vsub = msub_vsub(msub);
char buf[128];
llist_for_each_entry(conn, &gsmnet->ran_conns, entry) if (!(*idx))
lnum++; vty_out(vty,
"_Subscriber_______________________________________ _LAC_ _RAN___________________"
if (lnum) " _MSC-A_state_________ _MSC-A_use_%s",
vty_out(vty, "--ConnId RAN --LAC Use --Tokens C A5 State ------------ Subscriber%s",
VTY_NEWLINE); VTY_NEWLINE);
} (*idx)++;
static void vty_dump_one_conn(struct vty *vty, const struct ran_conn *conn) vty_out(vty, "%50s %5u %23s %20s %d=%s%s",
{ vlr_subscr_short_name(msub_vsub(msub), 50),
vty_out(vty, "%08x %3s %5u %3u %08x %c /%1u %27s %22s%s", vsub ? vsub->cgi.lai.lac : 0,
conn->a.conn_id, msub_ran_conn_name(msub),
osmo_rat_type_name(conn->via_ran), osmo_fsm_inst_state_name(msc_a->c.fi),
conn->lac, osmo_use_count_total(&msc_a->use_count),
conn->use_count, osmo_use_count_name_buf(buf, sizeof(buf), &msc_a->use_count),
conn->use_tokens,
conn->received_cm_service_request ? 'C' : '-',
conn->geran_encr.alg_id,
conn->fi ? osmo_fsm_inst_state_name(conn->fi) : "-",
conn->vsub ? vlr_subscr_name(conn->vsub) : "-",
VTY_NEWLINE); VTY_NEWLINE);
} }
DEFUN(show_msc_conn, show_msc_conn_cmd, DEFUN(show_msc_conn, show_msc_conn_cmd,
"show connection", SHOW_STR "Subscriber Connections\n") "show connection", SHOW_STR "Subscriber Connections\n")
{ {
struct ran_conn *conn; struct msub *msub;
int idx = 0;
vty_conn_hdr(vty); llist_for_each_entry(msub, &msub_list, entry) {
llist_for_each_entry(conn, &gsmnet->ran_conns, entry) vty_dump_one_conn(vty, msub, &idx);
vty_dump_one_conn(vty, conn); }
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static void vty_trans_hdr(struct vty *vty) static void vty_trans_hdr(struct vty *vty)
{ {
unsigned lnum = 0; if (llist_empty(&gsmnet->trans_list))
struct gsm_trans *trans; return;
llist_for_each_entry(trans, &gsmnet->trans_list, entry) vty_out(vty,
lnum++; "_Subscriber_______________________________________ _RAN___________________"
" _P__ TI CallRef_ _state_%s",
if (lnum) VTY_NEWLINE);
vty_out(vty, "--ConnId -P TI -CallRef [--- Proto ---] ------------ Subscriber%s",
VTY_NEWLINE);
} }
static const char *get_trans_proto_str(const struct gsm_trans *trans) static const char *get_trans_proto_str(const struct gsm_trans *trans)
{ {
static char buf[256]; static char buf[256];
switch (trans->protocol) { switch (trans->type) {
case GSM48_PDISC_CC: case TRANS_CC:
snprintf(buf, sizeof(buf), "%s %4u %4u", snprintf(buf, sizeof(buf), "%s %4u %4u",
gsm48_cc_state_name(trans->cc.state), gsm48_cc_state_name(trans->cc.state),
trans->cc.Tcurrent, trans->cc.Tcurrent,
trans->cc.T308_second); trans->cc.T308_second);
break; break;
case GSM48_PDISC_SMS: case TRANS_SMS:
snprintf(buf, sizeof(buf), "%s %s", snprintf(buf, sizeof(buf), "%s %s",
gsm411_cp_state_name(trans->sms.smc_inst.cp_state), gsm411_cp_state_name(trans->sms.smc_inst.cp_state),
gsm411_rp_state_name(trans->sms.smr_inst.rp_state)); gsm411_rp_state_name(trans->sms.smr_inst.rp_state));
@ -646,13 +701,13 @@ static const char *get_trans_proto_str(const struct gsm_trans *trans)
static void vty_dump_one_trans(struct vty *vty, const struct gsm_trans *trans) static void vty_dump_one_trans(struct vty *vty, const struct gsm_trans *trans)
{ {
vty_out(vty, "%08x %s %02u %08x [%s] %22s%s", vty_out(vty, "%50s %23s %4s %02u %08x %s%s",
trans->conn ? trans->conn->a.conn_id : 0, vlr_subscr_short_name(msc_a_vsub(trans->msc_a), 50),
gsm48_pdisc_name(trans->protocol), msub_ran_conn_name(trans->msc_a->c.msub),
trans_type_name(trans->type),
trans->transaction_id, trans->transaction_id,
trans->callref, trans->callref,
get_trans_proto_str(trans), get_trans_proto_str(trans),
trans->vsub ? vlr_subscr_name(trans->vsub) : "-",
VTY_NEWLINE); VTY_NEWLINE);
} }
@ -715,18 +770,6 @@ static void subscr_dump_full_vty(struct vty *vty, struct vlr_subscr *vsub)
vty_out(vty, " LA allowed: %s%s", vty_out(vty, " LA allowed: %s%s",
vsub->la_allowed ? "true" : "false", VTY_NEWLINE); vsub->la_allowed ? "true" : "false", VTY_NEWLINE);
#if 0
/* TODO: add this to vlr_subscr? */
if (vsub->auth_info.auth_algo != AUTH_ALGO_NONE) {
struct gsm_auth_info *i = &vsub->auth_info;
vty_out(vty, " A3A8 algorithm id: %d%s",
i->auth_algo, VTY_NEWLINE);
vty_out(vty, " A3A8 Ki: %s%s",
osmo_hexdump(i->a3a8_ki, i->a3a8_ki_len),
VTY_NEWLINE);
}
#endif
if (vsub->last_tuple) { if (vsub->last_tuple) {
struct vlr_auth_tuple *t = vsub->last_tuple; struct vlr_auth_tuple *t = vsub->last_tuple;
vty_out(vty, " A3A8 last tuple (used %d times):%s", vty_out(vty, " A3A8 last tuple (used %d times):%s",
@ -762,9 +805,11 @@ static void subscr_dump_full_vty(struct vty *vty, struct vlr_subscr *vsub)
/* Connection */ /* Connection */
if (vsub->msc_conn_ref) { if (vsub->msc_conn_ref) {
struct ran_conn *conn = vsub->msc_conn_ref; struct msub *msub = vsub->msc_conn_ref;
vty_conn_hdr(vty); int idx = 0;
vty_dump_one_conn(vty, conn); if (msub) {
vty_dump_one_conn(vty, msub, &idx);
}
} }
/* Transactions */ /* Transactions */
@ -1214,7 +1259,7 @@ DEFUN(subscriber_ussd_notify,
"Text of USSD message to send\n") "Text of USSD message to send\n")
{ {
char *text; char *text;
struct ran_conn *conn; struct msc_a *msc_a;
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]); struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
int level; int level;
@ -1231,19 +1276,19 @@ DEFUN(subscriber_ussd_notify,
return CMD_WARNING; return CMD_WARNING;
} }
conn = connection_for_subscr(vsub); msc_a = msc_a_for_vsub(vsub, true);
if (!conn) { if (!msc_a || msc_a->c.remote_to) {
vty_out(vty, "%% An active connection is required for %s %s%s", vty_out(vty, "%% An active connection and local MSC-A role is required for %s %s%s",
argv[0], argv[1], VTY_NEWLINE); argv[0], argv[1], VTY_NEWLINE);
vlr_subscr_put(vsub, VSUB_USE_VTY); vlr_subscr_put(vsub, VSUB_USE_VTY);
talloc_free(text); talloc_free(text);
return CMD_WARNING; return CMD_WARNING;
} }
msc_send_ussd_notify(conn, level, text); msc_send_ussd_notify(msc_a, level, text);
/* FIXME: since we don't allocate a transaction here, /* FIXME: since we don't allocate a transaction here,
* we use dummy GSM 04.07 transaction ID. */ * we use dummy GSM 04.07 transaction ID. */
msc_send_ussd_release_complete(conn, 0x00); msc_send_ussd_release_complete(msc_a, 0x00);
vlr_subscr_put(vsub, VSUB_USE_VTY); vlr_subscr_put(vsub, VSUB_USE_VTY);
talloc_free(text); talloc_free(text);
@ -1256,7 +1301,7 @@ DEFUN(subscriber_paging,
SUBSCR_HELP "Issue an empty Paging for the subscriber (for debugging)\n") SUBSCR_HELP "Issue an empty Paging for the subscriber (for debugging)\n")
{ {
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]); struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
struct subscr_request *req; struct paging_request *req;
if (!vsub) { if (!vsub) {
vty_out(vty, "%% No subscriber found for %s %s%s", vty_out(vty, "%% No subscriber found for %s %s%s",
@ -1264,7 +1309,8 @@ DEFUN(subscriber_paging,
return CMD_WARNING; return CMD_WARNING;
} }
req = subscr_request_conn(vsub, NULL, NULL, "manual Paging from VTY", SGSAP_SERV_IND_CS_CALL); req = paging_request_start(vsub, PAGING_CAUSE_CALL_CONVERSATIONAL,
NULL, NULL, "manual Paging from VTY");
if (req) if (req)
vty_out(vty, "%% paging subscriber%s", VTY_NEWLINE); vty_out(vty, "%% paging subscriber%s", VTY_NEWLINE);
else else
@ -1308,7 +1354,7 @@ DEFUN(subscriber_mstest_close,
"Loop Type F\n" "Loop Type F\n"
"Loop Type I\n") "Loop Type I\n")
{ {
struct ran_conn *conn; struct msc_a *msc_a;
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]); struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
const char *loop_str; const char *loop_str;
int loop_mode; int loop_mode;
@ -1322,15 +1368,15 @@ DEFUN(subscriber_mstest_close,
loop_str = argv[2]; loop_str = argv[2];
loop_mode = loop_by_char(loop_str[0]); loop_mode = loop_by_char(loop_str[0]);
conn = connection_for_subscr(vsub); msc_a = msc_a_for_vsub(vsub, true);
if (!conn) { if (!msc_a) {
vty_out(vty, "%% An active connection is required for %s %s%s", vty_out(vty, "%% An active connection is required for %s %s%s",
argv[0], argv[1], VTY_NEWLINE); argv[0], argv[1], VTY_NEWLINE);
vlr_subscr_put(vsub, VSUB_USE_VTY); vlr_subscr_put(vsub, VSUB_USE_VTY);
return CMD_WARNING; return CMD_WARNING;
} }
gsm0414_tx_close_tch_loop_cmd(conn, loop_mode); gsm0414_tx_close_tch_loop_cmd(msc_a, loop_mode);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@ -1341,7 +1387,7 @@ DEFUN(subscriber_mstest_open,
SUBSCR_HELP "Send a TS 04.14 MS Test Command to subscriber\n" SUBSCR_HELP "Send a TS 04.14 MS Test Command to subscriber\n"
"Open a TCH Loop inside the MS\n") "Open a TCH Loop inside the MS\n")
{ {
struct ran_conn *conn; struct msc_a *msc_a;
struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]); struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
if (!vsub) { if (!vsub) {
@ -1350,15 +1396,15 @@ DEFUN(subscriber_mstest_open,
return CMD_WARNING; return CMD_WARNING;
} }
conn = connection_for_subscr(vsub); msc_a = msc_a_for_vsub(vsub, true);
if (!conn) { if (!msc_a) {
vty_out(vty, "%% An active connection is required for %s %s%s", vty_out(vty, "%% An active connection is required for %s %s%s",
argv[0], argv[1], VTY_NEWLINE); argv[0], argv[1], VTY_NEWLINE);
vlr_subscr_put(vsub, VSUB_USE_VTY); vlr_subscr_put(vsub, VSUB_USE_VTY);
return CMD_WARNING; return CMD_WARNING;
} }
gsm0414_tx_open_loop_cmd(conn); gsm0414_tx_open_loop_cmd(msc_a);
return CMD_SUCCESS; return CMD_SUCCESS;
} }
@ -1394,14 +1440,20 @@ static int scall_cbfn(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data) void *handler_data, void *signal_data)
{ {
struct scall_signal_data *sigdata = signal_data; struct scall_signal_data *sigdata = signal_data;
struct vty *vty = sigdata->data; struct vty *vty = sigdata->vty;
if (!vty_is_active(vty))
return 0;
switch (signal) { switch (signal) {
case S_SCALL_SUCCESS: case S_SCALL_SUCCESS:
vty_out(vty, "%% silent call success%s", VTY_NEWLINE); vty_out(vty, "%% Silent call success%s", VTY_NEWLINE);
break; break;
case S_SCALL_EXPIRED: case S_SCALL_FAILED:
vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE); vty_out(vty, "%% Silent call failed%s", VTY_NEWLINE);
break;
case S_SCALL_DETACHED:
vty_out(vty, "%% Silent call ended%s", VTY_NEWLINE);
break; break;
} }
return 0; return 0;
@ -1692,12 +1744,16 @@ void msc_vty_init(struct gsm_network *msc_network)
install_element(MSC_NODE, &cfg_msc_emergency_msisdn_cmd); install_element(MSC_NODE, &cfg_msc_emergency_msisdn_cmd);
install_element(MSC_NODE, &cfg_msc_sms_over_gsup_cmd); install_element(MSC_NODE, &cfg_msc_sms_over_gsup_cmd);
install_element(MSC_NODE, &cfg_msc_no_sms_over_gsup_cmd); install_element(MSC_NODE, &cfg_msc_no_sms_over_gsup_cmd);
install_element(MSC_NODE, &cfg_msc_handover_number_range_cmd);
neighbor_ident_vty_init(msc_network);
mgcp_client_vty_init(msc_network, MSC_NODE, &msc_network->mgw.conf); mgcp_client_vty_init(msc_network, MSC_NODE, &msc_network->mgw.conf);
#ifdef BUILD_IU #ifdef BUILD_IU
ranap_iu_vty_init(MSC_NODE, &msc_network->iu.rab_assign_addr_enc); ranap_iu_vty_init(MSC_NODE, (enum ranap_nsap_addr_enc*)&msc_network->iu.rab_assign_addr_enc);
#endif #endif
sgs_vty_init(); sgs_vty_init();
osmo_fsm_vty_add_cmds(); osmo_fsm_vty_add_cmds();
osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL); osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL);

590
src/libmsc/msub.c Normal file
View File

@ -0,0 +1,590 @@
/* Manage all MSC roles of a connected subscriber (MSC-A, MSC-I, MSC-T) */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/gsm/gsm48.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_roles.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/msc_i.h>
#include <osmocom/msc/msc_t.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/e_link.h>
const struct value_string msc_role_names[] = {
{ MSC_ROLE_A, "MSC-A" },
{ MSC_ROLE_I, "MSC-I" },
{ MSC_ROLE_T, "MSC-T" },
{}
};
LLIST_HEAD(msub_list);
#define for_each_msub_role(msub, role_idx) \
for ((role_idx) = 0; (role_idx) < ARRAY_SIZE((msub)->role); (role_idx)++) \
if ((msub)->role[role_idx])
enum msub_fsm_state {
MSUB_ST_ACTIVE,
MSUB_ST_TERMINATING,
};
enum msub_fsm_event {
MSUB_EV_ROLE_TERMINATED,
};
static void msub_check_for_release(struct osmo_fsm_inst *fi)
{
struct msub *msub = fi->priv;
struct msc_role_common *msc_role_a_c;
enum msc_role role_idx;
int role_present[MSC_ROLES_COUNT] = {};
struct osmo_fsm_inst *child;
/* See what child FSMs are still present. A caller might exchange roles by first allocating a new one as child
* of this FSM, and then exchanging the msub->role[] pointer. Even though the currently active role is removing
* itself from msub, we can still see whether another one is pending as a child of this msub. */
llist_for_each_entry(child, &fi->proc.children, proc.child) {
struct msc_role_common *c = child->priv;
role_present[c->role]++;
if (c->role == MSC_ROLE_A)
msc_role_a_c = c;
}
/* Log. */
for (role_idx = 0; role_idx < ARRAY_SIZE(role_present); role_idx++) {
if (!role_present[role_idx])
continue;
LOG_MSUB(msub, LOGL_DEBUG, "%d %s still active\n", role_present[role_idx], msc_role_name(role_idx));
}
/* To remain valid, there must be both an MSC-A role and one of MSC-I or MSC-T;
* except, SGs connections need no MSC-I or MSC-T. */
if (role_present[MSC_ROLE_A]
&& (role_present[MSC_ROLE_I] || role_present[MSC_ROLE_T]
|| (msc_role_a_c && msc_role_a_c->ran->type == OSMO_RAT_EUTRAN_SGS)))
return;
/* The subscriber has become invalid. Go to terminating state to clearly signal that this msub is definitely
* going now. */
osmo_fsm_inst_state_chg(fi, MSUB_ST_TERMINATING, 0, 0);
}
void msub_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msub *msub = fi->priv;
struct osmo_fsm_inst *role_fi;
switch (event) {
case MSUB_EV_ROLE_TERMINATED:
role_fi = data;
/* Role implementations are required to pass their own osmo_fsm_inst pointer to osmo_fsm_inst_term(). */
msub_remove_role(msub, role_fi);
msub_check_for_release(fi);
return;
default:
return;
}
}
void msub_fsm_terminating_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
void msub_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct msub *msub = fi->priv;
LOG_MSUB(msub, LOGL_DEBUG, "Free\n");
msub_set_vsub(msub, NULL);
llist_del(&msub->entry);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state msub_fsm_states[] = {
[MSUB_ST_ACTIVE] = {
.name = "active",
.in_event_mask = S(MSUB_EV_ROLE_TERMINATED),
.out_state_mask = S(MSUB_ST_TERMINATING),
.action = msub_fsm_active,
},
[MSUB_ST_TERMINATING] = {
.name = "terminating",
.onenter = msub_fsm_terminating_onenter,
},
};
static const struct value_string msub_fsm_event_names[] = {
OSMO_VALUE_STRING(MSUB_EV_ROLE_TERMINATED),
{}
};
struct osmo_fsm msub_fsm = {
.name = "msub_fsm",
.states = msub_fsm_states,
.num_states = ARRAY_SIZE(msub_fsm_states),
.log_subsys = DMSC,
.event_names = msub_fsm_event_names,
.cleanup = msub_fsm_cleanup,
};
static __attribute__((constructor)) void msub_fsm_init()
{
OSMO_ASSERT(osmo_fsm_register(&msub_fsm) == 0);
}
struct msc_role_common *_msub_role_alloc(struct msub *msub, enum msc_role role, struct osmo_fsm *role_fsm,
size_t struct_size, const char *struct_name, struct ran_infra *ran)
{
struct osmo_fsm_inst *fi;
struct msc_role_common *c;
fi = osmo_fsm_inst_alloc_child(role_fsm, msub->fi, MSUB_EV_ROLE_TERMINATED);
OSMO_ASSERT(fi);
c = (struct msc_role_common*)talloc_named_const(fi, struct_size, struct_name);
OSMO_ASSERT(c);
memset(c, 0, struct_size);
fi->priv = c;
*c = (struct msc_role_common){
.role = role,
.fi = fi,
.ran = ran,
};
msub_set_role(msub, fi);
return c;
}
struct msub *msub_alloc(struct gsm_network *net)
{
struct msub *msub;
struct osmo_fsm_inst *msub_fi = osmo_fsm_inst_alloc(&msub_fsm, net, NULL, LOGL_DEBUG, NULL);
OSMO_ASSERT(msub_fi);
msub = talloc(msub_fi, struct msub);
OSMO_ASSERT(msub);
msub_fi->priv = msub;
*msub = (struct msub){
.net = net,
.fi = msub_fi,
};
llist_add_tail(&msub->entry, &msub_list);
return msub;
}
/* Careful: the subscriber may not yet be authenticated, or may already be in release. Better use
* msc_a_for_vsub(for_vsub, true) to make sure you don't use an invalid conn. */
struct msub *msub_for_vsub(const struct vlr_subscr *for_vsub)
{
struct msub *msub;
if (!for_vsub)
return NULL;
llist_for_each_entry(msub, &msub_list, entry) {
if (msub->vsub == for_vsub)
return msub;
}
return NULL;
}
const char *msub_name(const struct msub *msub)
{
return vlr_subscr_name(msub? msub->vsub : NULL);
}
void msub_set_role(struct msub *msub, struct osmo_fsm_inst *msc_role)
{
struct osmo_fsm_inst *prev_role;
struct msc_role_common *c;
OSMO_ASSERT(msc_role);
c = msc_role->priv;
prev_role = msub->role[c->role];
if (prev_role)
LOGPFSML(prev_role, LOGL_DEBUG, "Replaced by another %s\n", msc_role_name(c->role));
c->msub = msub;
msub->role[c->role] = msc_role;
msub_update_id(msub);
if (prev_role) {
struct msc_role_common *prev_c = prev_role->priv;
switch (prev_c->role) {
case MSC_ROLE_I:
msc_i_clear(prev_role->priv);
break;
case MSC_ROLE_T:
msc_t_clear(prev_role->priv);
break;
default:
osmo_fsm_inst_term(prev_role, OSMO_FSM_TERM_REQUEST, prev_role);
break;
}
}
}
void msub_remove_role(struct msub *msub, struct osmo_fsm_inst *fi)
{
enum msc_role idx;
struct msc_role_common *c;
if (!msub || !fi)
return;
c = fi->priv;
LOG_MSUB(msub, LOGL_DEBUG, "%s terminated\n", msc_role_name(c->role));
for_each_msub_role(msub, idx) {
if (msub->role[idx] == fi)
msub->role[idx] = NULL;
}
}
struct msc_a *msub_msc_a(const struct msub *msub)
{
struct osmo_fsm_inst *fi;
if (!msub)
return NULL;
fi = msub->role[MSC_ROLE_A];
if (!fi)
return NULL;
return (struct msc_a*)fi->priv;
}
struct msc_i *msub_msc_i(const struct msub *msub)
{
struct osmo_fsm_inst *fi;
if (!msub)
return NULL;
fi = msub->role[MSC_ROLE_I];
if (!fi)
return NULL;
return (struct msc_i*)fi->priv;
}
struct msc_t *msub_msc_t(const struct msub *msub)
{
struct osmo_fsm_inst *fi;
if (!msub)
return NULL;
fi = msub->role[MSC_ROLE_T];
if (!fi)
return NULL;
return (struct msc_t*)fi->priv;
}
/* Return the ran_conn of the MSC-I role, if available. If the MSC-I role is handled by a remote MSC, return NULL. */
struct ran_conn *msub_ran_conn(const struct msub *msub)
{
struct msc_i *msc_i = msub_msc_i(msub);
if (!msc_i)
return NULL;
return msc_i->ran_conn;
}
static struct ran_infra *msub_ran(const struct msub *msub)
{
int i;
struct msc_role_common *c;
for (i = 0; i < MSC_ROLES_COUNT; i++) {
if (!msub->role[i])
continue;
c = msub->role[i]->priv;
if (!c->ran)
continue;
return c->ran;
}
return &msc_ran_infra[OSMO_RAT_UNKNOWN];
}
const char *msub_ran_conn_name(const struct msub *msub)
{
struct msc_i *msc_i = msub_msc_i(msub);
struct msc_t *msc_t = msub_msc_t(msub);
if (msc_i && msc_i->c.remote_to)
return e_link_name(msc_i->c.remote_to);
if (msc_i && msc_i->ran_conn)
return ran_conn_name(msc_i->ran_conn);
if (msc_t && msc_t->c.remote_to)
return e_link_name(msc_t->c.remote_to);
if (msc_t && msc_t->ran_conn)
return ran_conn_name(msc_t->ran_conn);
return osmo_rat_type_name(msub_ran(msub)->type);
}
int msub_set_vsub(struct msub *msub, struct vlr_subscr *vsub)
{
OSMO_ASSERT(msub);
if (msub->vsub == vsub)
return 0;
if (msub->vsub && vsub) {
LOG_MSUB(msub, LOGL_ERROR,
"Changing a connection's VLR Subscriber is not allowed: not changing to %s\n",
vlr_subscr_name(vsub));
return -ENOTSUP;
}
if (vsub) {
struct msub *other_msub = msub_for_vsub(vsub);
if (other_msub) {
struct msc_a *msc_a = msub_msc_a(msub);
struct msc_a *other_msc_a = msub_msc_a(other_msub);
LOG_MSC_A(msc_a, LOGL_ERROR,
"Cannot associate with VLR subscr, another connection is already active%s%s\n",
other_msc_a ? " at " : "", other_msc_a ? other_msc_a->c.fi->id : "");
LOG_MSC_A(other_msc_a, LOGL_ERROR, "Attempt to associate a second subscriber connection%s%s\n",
msc_a ? " at " : "", msc_a ? msc_a->c.fi->id : "");
if (other_msc_a && msc_a_in_release(other_msc_a)) {
LOG_MSC_A(other_msc_a, LOGL_ERROR,
"Another connection for this subscriber is coming up, since this"
" is already in release, forcefully discarding it\n");
osmo_fsm_inst_term(other_msc_a->c.fi, OSMO_FSM_TERM_ERROR, other_msc_a->c.fi);
/* Count this as "recovered from duplicate connection" error and do associate. */
} else
return -EINVAL;
}
}
if (msub->vsub) {
vlr_subscr_put(msub->vsub, VSUB_USE_MSUB);
msub->vsub = NULL;
}
if (vsub) {
vlr_subscr_get(vsub, VSUB_USE_MSUB);
msub->vsub = vsub;
vsub->cs.attached_via_ran = msub_ran(msub)->type;
msub_update_id(msub);
}
return 0;
}
struct vlr_subscr *msub_vsub(const struct msub *msub)
{
return msub ? msub->vsub : NULL;
}
struct gsm_network *msub_net(const struct msub *msub)
{
OSMO_ASSERT(msub->net);
return msub->net;
}
int msub_role_to_role_event(struct msub *msub, enum msc_role from_role, enum msc_role to_role)
{
switch (from_role) {
case MSC_ROLE_A:
switch (to_role) {
case MSC_ROLE_I:
return MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
case MSC_ROLE_T:
return MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
default:
break;
}
break;
case MSC_ROLE_I:
switch (to_role) {
case MSC_ROLE_A:
return MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST;
default:
break;
}
break;
case MSC_ROLE_T:
switch (to_role) {
case MSC_ROLE_A:
return MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST;
default:
break;
}
break;
default:
break;
}
LOG_MSUB(msub, LOGL_ERROR, "Cannot tx DTAP from %s to %s\n", msc_role_name(from_role), msc_role_name(to_role));
return -1;
}
/* The caller retains ownership of the an_apdu_msg -- don't forget to msgb_free() it. */
int _msub_role_dispatch(struct msub *msub, enum msc_role to_role, uint32_t to_role_event, const struct an_apdu *an_apdu,
const char *file, int line)
{
struct osmo_fsm_inst *to_fi = msub->role[to_role];
if (!to_fi) {
LOG_MSUB_CAT_SRC(msub, DMSC, LOGL_ERROR, file, line,
"Cannot tx event to %s, no such role defined\n", msc_role_name(to_role));
return -EINVAL;
}
return _osmo_fsm_inst_dispatch(to_fi, to_role_event, (void*)an_apdu, file, line);
}
/* The caller retains ownership of the an_apdu_msg -- don't forget to msgb_free() it. */
int msub_tx_an_apdu(struct msub *msub, enum msc_role from_role, enum msc_role to_role, struct an_apdu *an_apdu)
{
int event = msub_role_to_role_event(msub, from_role, to_role);
if (event < 0)
return event;
return msub_role_dispatch(msub, to_role, event, an_apdu);
}
static void _msub_update_id(struct msub *msub, const char *subscr_name)
{
enum msc_role idx;
struct msc_a *msc_a = msub_msc_a(msub);
struct vlr_subscr *vsub = msub_vsub(msub);
const char *compl_l3_name = NULL;
char id[128];
if (msc_a)
compl_l3_name = get_value_string_or_null(complete_layer3_type_names, msc_a->complete_layer3_type);
if (!compl_l3_name)
compl_l3_name = "no-compl-l3";
snprintf(id, sizeof(id), "%s:%s:%s", subscr_name, msub_ran_conn_name(msub), compl_l3_name);
osmo_identifier_sanitize_buf(id, NULL, '-');
for_each_msub_role(msub, idx) {
osmo_fsm_inst_update_id(msub->role[idx], id);
}
if (vsub) {
if (vsub->lu_fsm)
osmo_fsm_inst_update_id(vsub->lu_fsm, id);
if (vsub->auth_fsm)
osmo_fsm_inst_update_id(vsub->auth_fsm, id);
if (vsub->proc_arq_fsm)
osmo_fsm_inst_update_id(vsub->proc_arq_fsm, id);
}
}
/* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */
void msub_update_id_from_mi(struct msub *msub, const uint8_t mi[], uint8_t mi_len)
{
_msub_update_id(msub, osmo_mi_name(mi, mi_len));
}
/* Update msub->fi id string from current msub->vsub and msub->complete_layer3_type. */
void msub_update_id(struct msub *msub)
{
if (!msub)
return;
_msub_update_id(msub, vlr_subscr_name(msub->vsub));
}
/* Iterate all msub instances that are relevant for this subscriber, and update FSM ID strings for all of the FSM
* instances. */
void msub_update_id_for_vsub(struct vlr_subscr *for_vsub)
{
struct msub *msub;
if (!for_vsub)
return;
llist_for_each_entry(msub, &msub_list, entry) {
if (msub->vsub == for_vsub)
msub_update_id(msub);
}
}
void msc_role_forget_conn(struct osmo_fsm_inst *role, struct ran_conn *conn)
{
struct msc_i *old_i = role->priv;
struct msc_t *old_t = role->priv;
struct msc_role_common *c = role->priv;
struct ran_conn **conn_p = NULL;
switch (c->role) {
case MSC_ROLE_I:
conn_p = &old_i->ran_conn;
break;
case MSC_ROLE_T:
conn_p = &old_t->ran_conn;
break;
default:
break;
}
if (!conn_p)
return;
if (*conn_p != conn)
return;
(*conn_p)->msc_role = NULL;
*conn_p = NULL;
}
struct msgb *msc_role_ran_encode(struct osmo_fsm_inst *fi, const struct ran_msg *ran_msg)
{
struct msc_role_common *c = fi->priv;
struct msgb *msg;
if (!c->ran->ran_encode) {
LOGPFSML(fi, LOGL_ERROR, "Cannot encode %s: no NAS encoding function defined for RAN type %s\n",
ran_msg_type_name(ran_msg->msg_type), osmo_rat_type_name(c->ran->type));
return NULL;
}
msg = c->ran->ran_encode(fi, ran_msg);
if (!msg)
LOGPFSML(fi, LOGL_ERROR, "Failed to encode %s\n", ran_msg_type_name(ran_msg->msg_type));
return msg;
}
int msc_role_ran_decode(struct osmo_fsm_inst *fi, const struct an_apdu *an_apdu,
ran_decode_cb_t decode_cb, void *decode_cb_data)
{
struct ran_dec ran_dec;
struct msc_role_common *c = fi->priv;
if (!an_apdu) {
LOGPFSML(fi, LOGL_ERROR, "NULL AN-APDU\n");
return -EINVAL;
}
if (an_apdu->an_proto != c->ran->an_proto) {
LOGPFSML(fi, LOGL_ERROR, "Unexpected AN-APDU protocol: %s\n", an_proto_name(an_apdu->an_proto));
return -EINVAL;
}
if (!an_apdu->msg) {
LOGPFSML(fi, LOGL_DEBUG, "No PDU in this AN-APDU\n");
return 0;
}
ran_dec = (struct ran_dec) {
.caller_fi = fi,
.caller_data = decode_cb_data,
.decode_cb = decode_cb,
};
if (!c->ran->ran_dec_l2) {
LOGPFSML(fi, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n",
osmo_rat_type_name(c->ran->type));
return -ENOTSUP;
}
return c->ran->ran_dec_l2(&ran_dec, an_apdu->msg);
}

191
src/libmsc/neighbor_ident.c Normal file
View File

@ -0,0 +1,191 @@
/* Manage identity of neighboring BSS cells for inter-MSC handover. */
/*
* (C) 2018-2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
* Author: Stefan Sperling <ssperling@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/msc/neighbor_ident.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/sccp_ran.h>
#include <osmocom/msc/cell_id_list.h>
/* XXX greater than or equal to IPA_STRING_MAX (libosmocore) and MAX_PC_STR_LEN (libosmo-sccp). */
#define NEIGHBOR_IDENT_ADDR_STRING_MAX 64
static struct gsm_network *gsmnet;
void neighbor_ident_init(struct gsm_network *net)
{
gsmnet = net;
INIT_LLIST_HEAD(&gsmnet->neighbor_ident_list);
}
int msc_ipa_name_from_str(struct msc_ipa_name *min, const char *name)
{
int rc = osmo_strlcpy(min->buf, name, sizeof(min->buf));
if (rc >= sizeof(min->buf)) {
min->len = 0;
return -1;
}
min->len = rc;
return 0;
}
int msc_ipa_name_cmp(const struct msc_ipa_name *a, const struct msc_ipa_name *b)
{
size_t cmp_len;
int rc;
if (a == b)
return 0;
if (!a || !b)
return a ? 1 : -1;
cmp_len = OSMO_MIN(sizeof(a->buf), OSMO_MIN(a->len, b->len));
if (!cmp_len)
rc = 0;
else
rc = memcmp(a->buf, b->buf, cmp_len);
if (rc)
return rc;
if (a->len < b->len)
return -1;
if (a->len > b->len)
return 1;
return 0;
}
const char *neighbor_ident_addr_name(const struct neighbor_ident_addr *nia)
{
static char buf[128];
struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
OSMO_STRBUF_PRINTF(sb, "%s-", osmo_rat_type_name(nia->ran_type));
switch (nia->type) {
case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
OSMO_STRBUF_PRINTF(sb, "localRAN-%s", nia->local_ran_peer_pc_str);
break;
case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
OSMO_STRBUF_PRINTF(sb, "remoteMSC-");
OSMO_STRBUF_APPEND_NOLEN(sb, osmo_escape_str_buf2, nia->remote_msc_ipa_name.buf,
nia->remote_msc_ipa_name.len);
break;
default:
return NULL;
}
return buf;
}
const struct neighbor_ident_entry *neighbor_ident_add(struct llist_head *ni_list,
const struct neighbor_ident_addr *nia,
const struct gsm0808_cell_id *cid)
{
struct neighbor_ident_entry *nie;
if (!ni_list)
return NULL;
nie = (struct neighbor_ident_entry*)neighbor_ident_find_by_addr(ni_list, nia);
if (!nie) {
nie = talloc_zero(gsmnet, struct neighbor_ident_entry);
OSMO_ASSERT(nie);
*nie = (struct neighbor_ident_entry){
.addr = *nia,
};
INIT_LLIST_HEAD(&nie->cell_ids);
llist_add_tail(&nie->entry, ni_list);
}
cell_id_list_add_cell(nie, &nie->cell_ids, cid);
return nie;
}
const struct neighbor_ident_entry *neighbor_ident_find_by_cell(const struct llist_head *ni_list,
enum osmo_rat_type ran_type,
const struct gsm0808_cell_id *cell_id)
{
struct neighbor_ident_entry *e;
llist_for_each_entry(e, ni_list, entry) {
if (ran_type != OSMO_RAT_UNKNOWN) {
if (e->addr.ran_type != ran_type)
continue;
}
if (!cell_id_list_find(&e->cell_ids, cell_id, 0, false))
continue;
return e;
}
return NULL;
}
const struct neighbor_ident_entry *neighbor_ident_find_by_addr(const struct llist_head *ni_list,
const struct neighbor_ident_addr *nia)
{
struct neighbor_ident_entry *e;
llist_for_each_entry(e, ni_list, entry) {
if (nia->ran_type != OSMO_RAT_UNKNOWN
&& e->addr.ran_type != nia->ran_type)
continue;
if (e->addr.type != nia->type)
continue;
switch (e->addr.type) {
case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
if (strcmp(e->addr.local_ran_peer_pc_str, nia->local_ran_peer_pc_str))
continue;
break;
case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
if (msc_ipa_name_cmp(&e->addr.remote_msc_ipa_name, &nia->remote_msc_ipa_name))
continue;
break;
default:
continue;
}
return e;
}
return NULL;
}
void neighbor_ident_del(const struct neighbor_ident_entry *nie)
{
struct neighbor_ident_entry *e = (struct neighbor_ident_entry*)nie;
llist_del(&e->entry);
talloc_free(e);
}
void neighbor_ident_clear(struct llist_head *ni_list)
{
struct neighbor_ident_entry *nie;
while ((nie = llist_first_entry_or_null(ni_list, struct neighbor_ident_entry, entry)))
neighbor_ident_del(nie);
}

View File

@ -0,0 +1,421 @@
/* Quagga VTY implementation to manage identity of neighboring BSS cells for inter-BSC handover. */
/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
* Author: Stefan Sperling <ssperling@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <osmocom/vty/command.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/msc/vty.h>
#include <osmocom/msc/neighbor_ident.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/ran_infra.h>
#include <osmocom/msc/cell_id_list.h>
#define NEIGHBOR_ADD_CMD "neighbor"
#define NEIGHBOR_ADD_DOC "Add Handover target configuration\n"
#define NEIGHBOR_DEL_CMD "no neighbor"
#define NEIGHBOR_DEL_DOC NO_STR "Remove Handover target\n"
#define NEIGHBOR_SHOW_CMD "show neighbor"
#define NEIGHBOR_SHOW_DOC SHOW_STR "Show Handover targets\n"
#define RAN_TYPE_PARAMS "(a|iu)"
#define RAN_TYPE_DOC "Neighbor on GERAN-A\n" "Neighbor on UTRAN-Iu\n"
#define RAN_PC_TOKEN "ran-pc"
#define MSC_IPA_NAME_TOKEN "msc-ipa-name"
#define HO_TARGET_PARAMS "("RAN_PC_TOKEN"|"MSC_IPA_NAME_TOKEN") RAN_PC_OR_MSC_IPA_NAME"
#define HO_TARGET_DOC "SCCP point code of RAN peer\n" "GSUP IPA name of target MSC\n" "Point code or MSC IPA name value\n"
#define LAC_PARAMS "lac <0-65535>"
#define LAC_ARGC 1
#define LAC_DOC "Handover target cell by LAC\n" "LAC\n"
#define LAC_CI_PARAMS "lac-ci <0-65535> <0-65535>"
#define LAC_CI_ARGC 2
#define LAC_CI_DOC "Handover target cell by LAC and CI\n" "LAC\n" "CI\n"
#define CGI_PARAMS "cgi <0-999> <0-999> <0-65535> <0-65535>"
#define CGI_ARGC 4
#define CGI_DOC "Handover target cell by Cell-Global Identifier (MCC, MNC, LAC, CI)\n" "MCC\n" "MNC\n" "LAC\n" "CI\n"
static struct gsm_network *gsmnet = NULL;
static void write_neighbor_ident_cell(struct vty *vty, const struct neighbor_ident_entry *e,
const struct gsm0808_cell_id *cid)
{
vty_out(vty, " " NEIGHBOR_ADD_CMD " ");
switch (e->addr.ran_type) {
case OSMO_RAT_GERAN_A:
vty_out(vty, "a");
break;
case OSMO_RAT_UTRAN_IU:
vty_out(vty, "iu");
break;
default:
vty_out(vty, "<Unsupported-RAN-type>");
break;
}
vty_out(vty, " ");
switch (cid->id_discr) {
case CELL_IDENT_LAC:
vty_out(vty, "lac %u", cid->id.lac);
break;
case CELL_IDENT_LAC_AND_CI:
vty_out(vty, "lac-ci %u %u",
cid->id.lac_and_ci.lac,
cid->id.lac_and_ci.ci);
break;
case CELL_IDENT_WHOLE_GLOBAL:
vty_out(vty, "cgi %s %s %u %u",
osmo_mcc_name(cid->id.global.lai.plmn.mcc),
osmo_mnc_name(cid->id.global.lai.plmn.mnc, cid->id.global.lai.plmn.mnc_3_digits),
cid->id.global.lai.lac,
cid->id.global.cell_identity);
break;
default:
vty_out(vty, "<Unsupported-Cell-Identity>");
break;
}
vty_out(vty, " ");
switch (e->addr.type) {
case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
vty_out(vty, RAN_PC_TOKEN " %s", e->addr.local_ran_peer_pc_str);
break;
case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
vty_out(vty, MSC_IPA_NAME_TOKEN " %s", osmo_escape_str(e->addr.remote_msc_ipa_name.buf,
e->addr.remote_msc_ipa_name.len));
break;
default:
vty_out(vty, "<Unsupported-target-type>");
break;
}
vty_out(vty, "%s", VTY_NEWLINE);
}
static void write_neighbor_ident_entry(struct vty *vty, const struct neighbor_ident_entry *e)
{
struct cell_id_list_entry *le;
llist_for_each_entry(le, &e->cell_ids, entry) {
write_neighbor_ident_cell(vty, e, &le->cell_id);
}
}
static void write_neighbor_ident_entry_by_cell(struct vty *vty, const struct neighbor_ident_entry *e,
const struct gsm0808_cell_id *cid)
{
struct cell_id_list_entry *le;
llist_for_each_entry(le, &e->cell_ids, entry) {
if (!gsm0808_cell_ids_match(&le->cell_id, cid, false))
continue;
write_neighbor_ident_cell(vty, e, &le->cell_id);
}
}
void neighbor_ident_vty_write(struct vty *vty)
{
const struct neighbor_ident_entry *e;
llist_for_each_entry(e, &gsmnet->neighbor_ident_list, entry) {
write_neighbor_ident_entry(vty, e);
}
}
void neighbor_ident_vty_write_by_ran_type(struct vty *vty, enum osmo_rat_type ran_type)
{
const struct neighbor_ident_entry *e;
llist_for_each_entry(e, &gsmnet->neighbor_ident_list, entry) {
if (e->addr.ran_type != ran_type)
continue;
write_neighbor_ident_entry(vty, e);
}
}
void neighbor_ident_vty_write_by_cell(struct vty *vty, enum osmo_rat_type ran_type, const struct gsm0808_cell_id *cid)
{
struct neighbor_ident_entry *e;
llist_for_each_entry(e, &gsmnet->neighbor_ident_list, entry) {
if (ran_type != OSMO_RAT_UNKNOWN
&& e->addr.ran_type != ran_type)
continue;
write_neighbor_ident_entry_by_cell(vty, e, cid);
}
}
static struct gsm0808_cell_id *parse_lac(struct vty *vty, const char **argv)
{
static struct gsm0808_cell_id cell_id;
cell_id = (struct gsm0808_cell_id){
.id_discr = CELL_IDENT_LAC,
.id.lac = atoi(argv[0]),
};
return &cell_id;
}
static struct gsm0808_cell_id *parse_lac_ci(struct vty *vty, const char **argv)
{
static struct gsm0808_cell_id cell_id;
cell_id = (struct gsm0808_cell_id){
.id_discr = CELL_IDENT_LAC_AND_CI,
.id.lac_and_ci = {
.lac = atoi(argv[0]),
.ci = atoi(argv[1]),
},
};
return &cell_id;
}
static struct gsm0808_cell_id *parse_cgi(struct vty *vty, const char **argv)
{
static struct gsm0808_cell_id cell_id;
cell_id = (struct gsm0808_cell_id){
.id_discr = CELL_IDENT_WHOLE_GLOBAL,
};
struct osmo_cell_global_id *cgi = &cell_id.id.global;
const char *mcc = argv[0];
const char *mnc = argv[1];
const char *lac = argv[2];
const char *ci = argv[3];
if (osmo_mcc_from_str(mcc, &cgi->lai.plmn.mcc)) {
vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
return NULL;
}
if (osmo_mnc_from_str(mnc, &cgi->lai.plmn.mnc, &cgi->lai.plmn.mnc_3_digits)) {
vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
return NULL;
}
cgi->lai.lac = atoi(lac);
cgi->cell_identity = atoi(ci);
return &cell_id;
}
static int add_neighbor(struct vty *vty, struct neighbor_ident_addr *addr, const struct gsm0808_cell_id *cell_id)
{
if (!neighbor_ident_add(&gsmnet->neighbor_ident_list, addr, cell_id)) {
vty_out(vty, "%% Error: cannot add cell %s to neighbor %s%s",
gsm0808_cell_id_name(cell_id), neighbor_ident_addr_name(addr),
VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
static enum osmo_rat_type parse_ran_type(struct vty *vty, const char *ran_type_str)
{
if (!strcmp(ran_type_str, "a"))
return OSMO_RAT_GERAN_A;
else if (!strcmp(ran_type_str, "iu"))
return OSMO_RAT_UTRAN_IU;
vty_out(vty, "%% Error: cannot parse RAN type argument %s%s",
osmo_quote_str(ran_type_str, -1), VTY_NEWLINE);
return OSMO_RAT_UNKNOWN;
}
static enum msc_neighbor_type parse_target_type(struct vty *vty, const char *target_type_str)
{
if (osmo_str_startswith(RAN_PC_TOKEN, target_type_str))
return MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER;
if (osmo_str_startswith(MSC_IPA_NAME_TOKEN, target_type_str))
return MSC_NEIGHBOR_TYPE_REMOTE_MSC;
vty_out(vty, "%% Unknown Handover target type: %s%s\n",
osmo_quote_str(target_type_str, -1), VTY_NEWLINE);
return MSC_NEIGHBOR_TYPE_NONE;
}
static int parse_ho_target_addr(struct vty *vty,
struct neighbor_ident_addr *nia,
enum osmo_rat_type ran_type,
const char **argv)
{
const char *target_type_str = argv[0];
const char *arg_str = argv[1];
int rc;
*nia = (struct neighbor_ident_addr){
.type = parse_target_type(vty, target_type_str),
.ran_type = ran_type,
};
if (nia->ran_type == OSMO_RAT_UNKNOWN)
return -1;
switch (nia->type) {
case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
rc = osmo_strlcpy(nia->local_ran_peer_pc_str, arg_str, sizeof(nia->local_ran_peer_pc_str));
if (rc < 1 || rc >= sizeof(nia->local_ran_peer_pc_str)) {
vty_out(vty, "%% Invalid RAN peer point-code string: %s%s", osmo_quote_str(arg_str, -1), VTY_NEWLINE);
return -1;
}
return 0;
case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
if (msc_ipa_name_from_str(&nia->remote_msc_ipa_name, arg_str)) {
vty_out(vty, "%% Invalid MSC IPA name: %s%s", osmo_quote_str(arg_str, -1), VTY_NEWLINE);
return -1;
}
return 0;
default:
return -1;
}
}
#define DEFUN_CELL(id_name, ID_NAME) \
\
DEFUN(cfg_neighbor_add_##id_name, cfg_neighbor_add_##id_name##_cmd, \
NEIGHBOR_ADD_CMD " "RAN_TYPE_PARAMS " " ID_NAME##_PARAMS " " HO_TARGET_PARAMS, \
NEIGHBOR_ADD_DOC RAN_TYPE_DOC ID_NAME##_DOC HO_TARGET_DOC) \
{ \
struct neighbor_ident_addr addr; \
if (parse_ho_target_addr(vty, &addr, \
parse_ran_type(vty, argv[0]), \
argv + 1 + ID_NAME##_ARGC)) \
return CMD_WARNING; \
return add_neighbor(vty, &addr, parse_##id_name(vty, argv + 1)); \
} \
\
DEFUN(show_neighbor_ran_##id_name, show_neighbor_ran_##id_name##_cmd, \
NEIGHBOR_SHOW_CMD " " RAN_TYPE_PARAMS " " ID_NAME##_PARAMS, \
NEIGHBOR_SHOW_DOC RAN_TYPE_DOC ID_NAME##_DOC RAN_TYPE_DOC) \
{ \
neighbor_ident_vty_write_by_cell(vty, \
parse_ran_type(vty, argv[0]), \
parse_##id_name(vty, argv + 1)); \
return CMD_SUCCESS; \
} \
\
DEFUN(show_neighbor_##id_name, show_neighbor_##id_name##_cmd, \
NEIGHBOR_SHOW_CMD " "ID_NAME##_PARAMS, \
NEIGHBOR_SHOW_DOC ID_NAME##_DOC) \
{ \
neighbor_ident_vty_write_by_cell(vty, OSMO_RAT_UNKNOWN, parse_##id_name(vty, argv)); \
return CMD_SUCCESS; \
}
DEFUN_CELL(lac, LAC)
DEFUN_CELL(lac_ci, LAC_CI)
DEFUN_CELL(cgi, CGI)
static int del_by_addr(struct vty *vty, const struct neighbor_ident_addr *addr)
{
const struct neighbor_ident_entry *e = neighbor_ident_find_by_addr(&gsmnet->neighbor_ident_list, addr);
if (!e) {
vty_out(vty, "%% Cannot remove, no such neighbor: %s%s",
neighbor_ident_addr_name(addr), VTY_NEWLINE);
return CMD_WARNING;
}
neighbor_ident_del(e);
vty_out(vty, "%% Removed neighbor %s%s", neighbor_ident_addr_name(addr), VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(cfg_del_neighbor, cfg_del_neighbor_cmd,
NEIGHBOR_DEL_CMD " " RAN_TYPE_PARAMS " "HO_TARGET_PARAMS,
NEIGHBOR_DEL_DOC RAN_TYPE_DOC HO_TARGET_DOC)
{
struct neighbor_ident_addr addr;
if (parse_ho_target_addr(vty, &addr,
parse_ran_type(vty, argv[0]),
argv + 1))
return CMD_WARNING;
return del_by_addr(vty, &addr);
}
DEFUN(show_neighbor_all, show_neighbor_all_cmd,
NEIGHBOR_SHOW_CMD,
NEIGHBOR_SHOW_DOC)
{
neighbor_ident_vty_write(vty);
return CMD_SUCCESS;
}
DEFUN(show_neighbor_ran, show_neighbor_ran_cmd,
NEIGHBOR_SHOW_CMD " " RAN_TYPE_PARAMS,
NEIGHBOR_SHOW_DOC RAN_TYPE_DOC)
{
neighbor_ident_vty_write_by_ran_type(vty, parse_ran_type(vty, argv[0]));
return CMD_SUCCESS;
}
DEFUN(show_neighbor, show_neighbor_cmd,
NEIGHBOR_SHOW_CMD " "RAN_TYPE_PARAMS " " HO_TARGET_PARAMS,
NEIGHBOR_SHOW_DOC RAN_TYPE_DOC HO_TARGET_DOC)
{
const struct neighbor_ident_entry *e;
struct neighbor_ident_addr addr;
if (parse_ho_target_addr(vty, &addr,
parse_ran_type(vty, argv[0]),
argv + 1))
return CMD_WARNING;
e = neighbor_ident_find_by_addr(&gsmnet->neighbor_ident_list, &addr);
if (e)
write_neighbor_ident_entry(vty, e);
else
vty_out(vty, "%% No such neighbor target%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
void neighbor_ident_vty_init(struct gsm_network *net)
{
gsmnet = net;
install_element(MSC_NODE, &cfg_neighbor_add_lac_cmd);
install_element(MSC_NODE, &cfg_neighbor_add_lac_ci_cmd);
install_element(MSC_NODE, &cfg_neighbor_add_cgi_cmd);
install_element(MSC_NODE, &cfg_del_neighbor_cmd);
install_element_ve(&show_neighbor_all_cmd);
install_element_ve(&show_neighbor_cmd);
install_element_ve(&show_neighbor_ran_cmd);
install_element_ve(&show_neighbor_ran_lac_cmd);
install_element_ve(&show_neighbor_ran_lac_ci_cmd);
install_element_ve(&show_neighbor_ran_cgi_cmd);
install_element_ve(&show_neighbor_lac_cmd);
install_element_ve(&show_neighbor_lac_ci_cmd);
install_element_ve(&show_neighbor_cgi_cmd);
}

View File

@ -1,227 +0,0 @@
/* main MSC management code... */
/*
* (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
* (C) 2010 by On-Waves
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/msc/debug.h>
#include <osmocom/msc/transaction.h>
#include <osmocom/msc/db.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/gsm_04_08.h>
#include <osmocom/msc/gsm_04_11.h>
#include <osmocom/msc/msc_mgcp.h>
#include "../../bscconfig.h"
#ifdef BUILD_IU
#include <osmocom/ranap/iu_client.h>
#else
#include <osmocom/msc/iu_dummy.h>
#endif
struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv)
{
struct gsm_network *net;
net = talloc_zero(ctx, struct gsm_network);
if (!net)
return NULL;
net->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
/* Permit a compile-time default of A5/3 and A5/1 */
net->a5_encryption_mask = (1 << 3) | (1 << 1);
/* Use 30 min periodic update interval as sane default */
net->t3212 = 5;
net->mncc_guard_timeout = 180;
net->ncss_guard_timeout = 30;
net->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT;
INIT_LLIST_HEAD(&net->trans_list);
INIT_LLIST_HEAD(&net->upqueue);
INIT_LLIST_HEAD(&net->ran_conns);
/* init statistics */
net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
if (!net->msc_ctrs) {
talloc_free(net);
return NULL;
}
net->active_calls = osmo_counter_alloc("msc.active_calls");
net->active_nc_ss = osmo_counter_alloc("msc.active_nc_ss");
net->mncc_recv = mncc_recv;
INIT_LLIST_HEAD(&net->a.bscs);
return net;
}
void gsm_network_set_mncc_sock_path(struct gsm_network *net, const char *mncc_sock_path)
{
if (net->mncc_sock_path)
talloc_free(net->mncc_sock_path);
net->mncc_sock_path = mncc_sock_path ? talloc_strdup(net, mncc_sock_path) : NULL;
}
/* Receive a SAPI-N-REJECT from BSC */
void ran_conn_sapi_n_reject(struct ran_conn *conn, int dlci)
{
int sapi = dlci & 0x7;
if (sapi == UM_SAPI_SMS)
gsm411_sapi_n_reject(conn);
}
/* receive a Level 3 Complete message.
* Ownership of the conn is completely passed to the conn FSM, i.e. for both acceptance and rejection,
* the conn FSM shall decide when to release this conn. It may already be discarded before this exits. */
void ran_conn_compl_l3(struct ran_conn *conn,
struct msgb *msg, uint16_t chosen_channel)
{
ran_conn_get(conn, RAN_CONN_USE_COMPL_L3);
gsm0408_dispatch(conn, msg);
ran_conn_put(conn, RAN_CONN_USE_COMPL_L3);
}
/* Receive a DTAP message from BSC */
void ran_conn_dtap(struct ran_conn *conn, struct msgb *msg)
{
ran_conn_get(conn, RAN_CONN_USE_DTAP);
gsm0408_dispatch(conn, msg);
ran_conn_put(conn, RAN_CONN_USE_DTAP);
}
/* Receive an ASSIGNMENT COMPLETE from BSC */
void msc_assign_compl(struct ran_conn *conn,
uint8_t rr_cause, uint8_t chosen_channel,
uint8_t encr_alg_id, uint8_t speec)
{
LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n");
}
/* Receive an ASSIGNMENT FAILURE from BSC */
void ran_conn_assign_fail(struct ran_conn *conn, uint8_t cause, uint8_t *rr_cause)
{
LOGPFSMSL(conn->fi, DRR, LOGL_ERROR, "Assignment Failure: cause=%u rr_cause=%u.\n",
cause, rr_cause ? *rr_cause : 0);
msc_mgcp_ass_fail(conn);
}
/* Receive a CLASSMARK CHANGE from BSC */
void ran_conn_classmark_chg(struct ran_conn *conn,
const uint8_t *cm2, uint8_t cm2_len,
const uint8_t *cm3, uint8_t cm3_len)
{
struct gsm_classmark *cm;
if (!conn->vsub)
cm = &conn->temporary_classmark;
else
cm = &conn->vsub->classmark;
if (cm2 && cm2_len) {
if (cm2_len > sizeof(cm->classmark2)) {
LOGP(DRR, LOGL_NOTICE, "%s: classmark2 is %u bytes, truncating at %zu bytes\n",
vlr_subscr_name(conn->vsub), cm2_len, sizeof(cm->classmark2));
cm2_len = sizeof(cm->classmark2);
}
cm->classmark2_len = cm2_len;
memcpy(cm->classmark2, cm2, cm2_len);
}
if (cm3 && cm3_len) {
if (cm3_len > sizeof(cm->classmark3)) {
LOGP(DRR, LOGL_NOTICE, "%s: classmark3 is %u bytes, truncating at %zu bytes\n",
vlr_subscr_name(conn->vsub), cm3_len, sizeof(cm->classmark3));
cm3_len = sizeof(cm->classmark3);
}
cm->classmark3_len = cm3_len;
memcpy(cm->classmark3, cm3, cm3_len);
}
/* bump subscr conn FSM in case it is waiting for a Classmark Update */
if (conn->fi->state == RAN_CONN_S_WAIT_CLASSMARK_UPDATE)
osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_CLASSMARK_UPDATE, NULL);
}
/* Receive a CIPHERING MODE COMPLETE from BSC */
void ran_conn_cipher_mode_compl(struct ran_conn *conn, struct msgb *msg, uint8_t alg_id)
{
struct vlr_ciph_result ciph_res = { .cause = VLR_CIPH_REJECT };
if (!conn) {
LOGP(DRR, LOGL_ERROR, "invalid: rx Ciphering Mode Complete on NULL conn\n");
return;
}
if (!conn->vsub) {
LOGP(DRR, LOGL_ERROR, "invalid: rx Ciphering Mode Complete for NULL subscr\n");
return;
}
DEBUGP(DRR, "%s: CIPHERING MODE COMPLETE\n", vlr_subscr_name(conn->vsub));
if (msg) {
struct gsm48_hdr *gh = msgb_l3(msg);
unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
struct tlv_parsed tp;
uint8_t mi_type;
if (!gh) {
LOGP(DRR, LOGL_ERROR, "invalid: msgb without l3 header\n");
return;
}
tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
/* bearer capability */
if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) {
mi_type = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)[0] & GSM_MI_TYPE_MASK;
if (mi_type == GSM_MI_TYPE_IMEISV
&& TLVP_LEN(&tp, GSM48_IE_MOBILE_ID) > 0) {
gsm48_mi_to_string(ciph_res.imeisv, sizeof(ciph_res.imeisv),
TLVP_VAL(&tp, GSM48_IE_MOBILE_ID),
TLVP_LEN(&tp, GSM48_IE_MOBILE_ID));
}
}
}
conn->geran_encr.alg_id = alg_id;
ciph_res.cause = VLR_CIPH_COMPL;
vlr_subscr_rx_ciph_res(conn->vsub, &ciph_res);
}
/* Receive a CLEAR REQUEST from BSC */
int ran_conn_clear_request(struct ran_conn *conn, uint32_t cause)
{
ran_conn_close(conn, cause);
return 1;
}
void msc_stop_paging(struct vlr_subscr *vsub)
{
DEBUGP(DPAG, "Paging can stop for %s\n", vlr_subscr_name(vsub));
/* tell BSCs and RNCs to stop paging? How? */
}

183
src/libmsc/paging.c Normal file
View File

@ -0,0 +1,183 @@
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/paging.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/ran_peer.h>
#include <osmocom/msc/sgs_iface.h>
#include <osmocom/msc/signal.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/transaction.h>
#define LOG_PAGING(vsub, paging_request, level, fmt, args ...) \
LOGP(DPAG, level, "Paging: %s%s%s: " fmt, \
vlr_subscr_name(vsub), paging_request ? " for " : "", paging_request ? (paging_request)->label : "", ## args)
#define VSUB_USE_PAGING "Paging"
const struct value_string paging_cause_names[] = {
{ PAGING_CAUSE_CALL_CONVERSATIONAL, "CALL_CONVERSATIONAL" },
{ PAGING_CAUSE_CALL_STREAMING, "CALL_STREAMING" },
{ PAGING_CAUSE_CALL_INTERACTIVE, "CALL_INTERACTIVE" },
{ PAGING_CAUSE_CALL_BACKGROUND, "CALL_BACKGROUND" },
{ PAGING_CAUSE_SIGNALLING_LOW_PRIO, "SIGNALLING_LOW_PRIO" },
{ PAGING_CAUSE_SIGNALLING_HIGH_PRIO, "SIGNALLING_HIGH_PRIO" },
{ PAGING_CAUSE_UNSPECIFIED, "UNSPECIFIED" },
{}
};
static void paging_response_timer_cb(void *data)
{
struct vlr_subscr *vsub = data;
paging_expired(vsub);
}
/* Execute a paging on the currently active RAN. Returns the number of
* delivered paging requests or -EINVAL in case of failure. */
static int msc_paging_request(struct paging_request *pr, struct vlr_subscr *vsub)
{
struct gsm_network *net = vsub->vlr->user_ctx;
/* The subscriber was last seen in subscr->lac. Find out which
* BSCs/RNCs are responsible and send them a paging request via open
* SCCP connections (if any). */
switch (vsub->cs.attached_via_ran) {
case OSMO_RAT_GERAN_A:
return ran_peers_down_paging(net->a.sri, CELL_IDENT_LAC, vsub, pr->cause);
case OSMO_RAT_UTRAN_IU:
return ran_peers_down_paging(net->iu.sri, CELL_IDENT_LAC, vsub, pr->cause);
case OSMO_RAT_EUTRAN_SGS:
return sgs_iface_tx_paging(vsub, sgs_serv_ind_from_paging_cause(pr->cause));
default:
break;
}
LOG_PAGING(vsub, pr, LOGL_ERROR, " Cannot page, subscriber not attached\n");
return -EINVAL;
}
struct paging_request *paging_request_start(struct vlr_subscr *vsub, enum paging_cause cause,
paging_cb_t paging_cb, struct gsm_trans *trans,
const char *label)
{
int rc;
struct paging_request *pr;
struct gsm_network *net = vsub->vlr->user_ctx;
pr = talloc_zero(vsub, struct paging_request);
OSMO_ASSERT(pr);
*pr = (struct paging_request){
.label = label,
.cause = cause,
.paging_cb = paging_cb,
.trans = trans,
};
if (vsub->cs.is_paging) {
LOG_PAGING(vsub, pr, LOGL_DEBUG, "Already paging, not starting another request\n");
} else {
LOG_PAGING(vsub, pr, LOGL_DEBUG, "Starting paging\n");
rc = msc_paging_request(pr, vsub);
if (rc <= 0) {
LOG_PAGING(vsub, pr, LOGL_ERROR, "Starting paging failed (rc=%d)\n", rc);
talloc_free(pr);
return NULL;
}
/* reduced on the first paging callback */
vlr_subscr_get(vsub, VSUB_USE_PAGING);
vsub->cs.is_paging = true;
osmo_timer_setup(&vsub->cs.paging_response_timer, paging_response_timer_cb, vsub);
osmo_timer_schedule(&vsub->cs.paging_response_timer, net->paging_response_timer, 0);
}
llist_add_tail(&pr->entry, &vsub->cs.requests);
return pr;
}
void paging_request_remove(struct paging_request *pr)
{
struct gsm_trans *trans = pr->trans;
struct vlr_subscr *vsub = trans ? trans->vsub : NULL;
LOG_PAGING(vsub, pr, LOGL_DEBUG, "Removing Paging Request\n");
if (pr->trans && pr->trans->paging_request == pr)
pr->trans->paging_request = NULL;
llist_del(&pr->entry);
talloc_free(pr);
}
static void paging_concludes(struct vlr_subscr *vsub, struct msc_a *msc_a)
{
struct paging_request *pr, *pr_next;
struct paging_signal_data sig_data;
osmo_timer_del(&vsub->cs.paging_response_timer);
llist_for_each_entry_safe(pr, pr_next, &vsub->cs.requests, entry) {
struct gsm_trans *trans = pr->trans;
paging_cb_t paging_cb = pr->paging_cb;
LOG_PAGING(vsub, pr, LOGL_DEBUG, "Paging Response action (%s)%s\n",
msc_a ? "success" : "expired",
paging_cb ? "" : " (no action defined)");
/* Remove the paging request before the paging_cb could deallocate e.g. the trans */
paging_request_remove(pr);
pr = NULL;
if (paging_cb)
paging_cb(msc_a, trans);
}
/* Inform parts of the system we don't know */
sig_data = (struct paging_signal_data){
.vsub = vsub,
.msc_a = msc_a,
};
osmo_signal_dispatch(SS_PAGING, msc_a ? S_PAGING_SUCCEEDED : S_PAGING_EXPIRED, &sig_data);
/* balanced with the moment we start paging */
if (vsub->cs.is_paging) {
vsub->cs.is_paging = false;
vlr_subscr_put(vsub, VSUB_USE_PAGING);
}
/* Handling of the paging requests has usually added transactions, which keep the msc_a connection active. If
* there are none, then this probably marks release of the connection. */
if (msc_a)
msc_a_put(msc_a, MSC_A_USE_PAGING_RESPONSE);
}
void paging_response(struct msc_a *msc_a)
{
paging_concludes(msc_a_vsub(msc_a), msc_a);
}
void paging_expired(struct vlr_subscr *vsub)
{
paging_concludes(vsub, NULL);
}

View File

@ -30,883 +30,139 @@
#include <osmocom/msc/debug.h> #include <osmocom/msc/debug.h>
#include <osmocom/msc/transaction.h> #include <osmocom/msc/transaction.h>
#include <osmocom/msc/signal.h> #include <osmocom/msc/signal.h>
#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/sgs_iface.h> #include <osmocom/msc/sgs_iface.h>
#include <osmocom/msc/iucs.h> #include <osmocom/msc/ran_peer.h>
#include <osmocom/msc/sccp_ran.h>
#include <osmocom/msc/ran_infra.h>
#include <osmocom/msc/msub.h>
#include "../../bscconfig.h" struct ran_conn *ran_conn_create_incoming(struct ran_peer *ran_peer, uint32_t sccp_conn_id)
#ifdef BUILD_IU
#include <osmocom/ranap/iu_client.h>
#else
#include <osmocom/msc/iu_dummy.h>
#endif
#define RAN_CONN_TIMEOUT 5 /* seconds */
static const struct value_string ran_conn_fsm_event_names[] = {
OSMO_VALUE_STRING(RAN_CONN_E_COMPLETE_LAYER_3),
OSMO_VALUE_STRING(RAN_CONN_E_CLASSMARK_UPDATE),
OSMO_VALUE_STRING(RAN_CONN_E_ACCEPTED),
OSMO_VALUE_STRING(RAN_CONN_E_COMMUNICATING),
OSMO_VALUE_STRING(RAN_CONN_E_RELEASE_WHEN_UNUSED),
OSMO_VALUE_STRING(RAN_CONN_E_MO_CLOSE),
OSMO_VALUE_STRING(RAN_CONN_E_CN_CLOSE),
OSMO_VALUE_STRING(RAN_CONN_E_UNUSED),
{ 0, NULL }
};
static void update_counters(struct osmo_fsm_inst *fi, bool conn_accepted)
{ {
struct ran_conn *conn = fi->priv; struct ran_conn *conn;
switch (conn->complete_layer3_type) {
case COMPLETE_LAYER3_LU: conn = talloc(ran_peer, struct ran_conn);
rate_ctr_inc(&conn->network->msc_ctrs->ctr[ OSMO_ASSERT(conn);
conn_accepted ? MSC_CTR_LOC_UPDATE_COMPLETED
: MSC_CTR_LOC_UPDATE_FAILED]); *conn = (struct ran_conn){
break; .ran_peer = ran_peer,
case COMPLETE_LAYER3_CM_SERVICE_REQ: .sccp_conn_id = sccp_conn_id,
rate_ctr_inc(&conn->network->msc_ctrs->ctr[ };
conn_accepted ? MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED
: MSC_CTR_CM_SERVICE_REQUEST_REJECTED]); llist_add(&conn->entry, &ran_peer->sri->ran_conns);
break; return conn;
case COMPLETE_LAYER3_PAGING_RESP:
rate_ctr_inc(&conn->network->msc_ctrs->ctr[
conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED
: MSC_CTR_PAGING_RESP_REJECTED]);
break;
default:
break;
}
} }
static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_accepted) struct ran_conn *ran_conn_create_outgoing(struct ran_peer *ran_peer)
{ {
struct ran_conn *conn = fi->priv; /* FIXME use method being developed in gerrit id Ifd55c6b7ed2558ff072042079cf45f5068a971de */
static uint32_t next_outgoing_conn_id = 2342;
uint32_t conn_id = 0;
int attempts = 1000;
bool already_used = true;
while (attempts--) {
struct ran_conn *conn;
update_counters(fi, conn_accepted); conn_id = next_outgoing_conn_id;
next_outgoing_conn_id++;
/* Trigger transactions that we paged for */ already_used = false;
if (conn->complete_layer3_type == COMPLETE_LAYER3_PAGING_RESP) { llist_for_each_entry(conn, &ran_peer->sri->ran_conns, entry) {
subscr_paging_dispatch(GSM_HOOK_RR_PAGING, if (conn->sccp_conn_id == conn_id) {
conn_accepted ? GSM_PAGING_SUCCEEDED : GSM_PAGING_EXPIRED, already_used = true;
NULL, conn, conn->vsub); break;
}
if (conn->complete_layer3_type == COMPLETE_LAYER3_CM_SERVICE_REQ
&& conn_accepted) {
conn->received_cm_service_request = true;
ran_conn_get(conn, RAN_CONN_USE_CM_SERVICE);
}
if (conn_accepted)
osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, conn->vsub);
}
static void log_close_event(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
enum gsm48_reject_value *cause = data;
/* The close event itself is logged by the FSM. We can only add the cause value, if present. */
if (!cause || !*cause)
return;
LOGPFSML(fi, LOGL_NOTICE, "Close event, cause: %s\n", gsm48_reject_value_name(*cause));
}
static void ran_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case RAN_CONN_E_COMPLETE_LAYER_3:
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_AUTH_CIPH, RAN_CONN_TIMEOUT, 0);
return;
case RAN_CONN_E_ACCEPTED:
evaluate_acceptance_outcome(fi, true);
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_ACCEPTED, RAN_CONN_TIMEOUT, 0);
return;
case RAN_CONN_E_MO_CLOSE:
case RAN_CONN_E_CN_CLOSE:
log_close_event(fi, event, data);
evaluate_acceptance_outcome(fi, false);
/* fall through */
case RAN_CONN_E_UNUSED:
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static void ran_conn_fsm_auth_ciph(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
/* If accepted, transition the state, all other cases mean failure. */
switch (event) {
case RAN_CONN_E_ACCEPTED:
evaluate_acceptance_outcome(fi, true);
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_ACCEPTED, RAN_CONN_TIMEOUT, 0);
return;
case RAN_CONN_E_UNUSED:
LOGPFSML(fi, LOGL_DEBUG, "Awaiting results for Auth+Ciph, overruling event %s\n",
osmo_fsm_event_name(fi->fsm, event));
return;
case RAN_CONN_E_MO_CLOSE:
case RAN_CONN_E_CN_CLOSE:
log_close_event(fi, event, data);
evaluate_acceptance_outcome(fi, false);
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
return;
default:
OSMO_ASSERT(false);
}
}
int ran_conn_classmark_request_then_cipher_mode_cmd(struct ran_conn *conn, bool umts_aka,
bool retrieve_imeisv)
{
int rc;
conn->geran_set_cipher_mode.umts_aka = umts_aka;
conn->geran_set_cipher_mode.retrieve_imeisv = retrieve_imeisv;
rc = a_iface_tx_classmark_request(conn);
if (rc) {
LOGP(DMM, LOGL_ERROR, "%s: cannot send BSSMAP Classmark Request\n",
vlr_subscr_name(conn->vsub));
return -EIO;
}
osmo_fsm_inst_state_chg(conn->fi, RAN_CONN_S_WAIT_CLASSMARK_UPDATE, RAN_CONN_TIMEOUT, 0);
return 0;
}
static void ran_conn_fsm_wait_classmark_update(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ran_conn *conn = fi->priv;
switch (event) {
case RAN_CONN_E_CLASSMARK_UPDATE:
/* Theoretically, this event can be used for requesting Classmark in various situations.
* So far though, the only time we send a Classmark Request is during Ciphering. As soon
* as more such situations arise, we need to add state to indicate what action should
* follow after a Classmark Update is received (e.g.
* ran_conn_classmark_request_then_cipher_mode_cmd() sets an enum value to indicate that
* Ciphering should continue afterwards). But right now, it is accurate to always
* continue with Ciphering: */
/* During Ciphering, we needed Classmark information. The Classmark Update has come in,
* go back into the Set Ciphering Command procedure. */
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_AUTH_CIPH, RAN_CONN_TIMEOUT, 0);
if (ran_conn_geran_set_cipher_mode(conn, conn->geran_set_cipher_mode.umts_aka,
conn->geran_set_cipher_mode.retrieve_imeisv)) {
LOGPFSML(fi, LOGL_ERROR,
"Sending Cipher Mode Command failed, aborting attach\n");
vlr_subscr_cancel_attach_fsm(conn->vsub, OSMO_FSM_TERM_ERROR,
GSM48_REJECT_NETWORK_FAILURE);
}
return;
case RAN_CONN_E_UNUSED:
LOGPFSML(fi, LOGL_DEBUG, "Awaiting results for Auth+Ciph, overruling event %s\n",
osmo_fsm_event_name(fi->fsm, event));
return;
case RAN_CONN_E_MO_CLOSE:
case RAN_CONN_E_CN_CLOSE:
log_close_event(fi, event, data);
evaluate_acceptance_outcome(fi, false);
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static bool ran_conn_fsm_has_active_transactions(struct osmo_fsm_inst *fi)
{
struct ran_conn *conn = fi->priv;
struct gsm_trans *trans;
if (conn->silent_call) {
LOGPFSML(fi, LOGL_DEBUG, "%s: silent call still active\n", __func__);
return true;
}
if (conn->received_cm_service_request) {
LOGPFSML(fi, LOGL_DEBUG, "%s: still awaiting first request after a CM Service Request\n",
__func__);
return true;
}
if (conn->vsub && !llist_empty(&conn->vsub->cs.requests)) {
struct subscr_request *sr;
if (!log_check_level(fi->fsm->log_subsys, LOGL_DEBUG)) {
llist_for_each_entry(sr, &conn->vsub->cs.requests, entry) {
LOGPFSML(fi, LOGL_DEBUG, "%s: still active: %s\n",
__func__, sr->label);
} }
} }
return true;
}
if ((trans = trans_has_conn(conn))) { if (!already_used)
LOGPFSML(fi, LOGL_DEBUG,
"%s: connection still has active transaction: %s\n",
__func__, gsm48_pdisc_name(trans->protocol));
return true;
}
return false;
}
static void ran_conn_fsm_accepted_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ran_conn *conn = fi->priv;
/* Stop Location Update expiry for this subscriber. While the subscriber
* has an open connection the LU expiry timer must remain disabled.
* Otherwise we would kick the subscriber off the network when the timer
* expires e.g. during a long phone call.
* The LU expiry timer will restart once the connection is closed. */
conn->vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION;
if (!ran_conn_fsm_has_active_transactions(fi))
osmo_fsm_inst_dispatch(fi, RAN_CONN_E_UNUSED, NULL);
}
static void ran_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case RAN_CONN_E_COMPLETE_LAYER_3:
/* When Authentication is off, we may already be in the Accepted state when the code
* evaluates the Compl L3. Simply ignore. This just cosmetically mutes the error log
* about the useless event. */
return;
case RAN_CONN_E_COMMUNICATING:
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_COMMUNICATING, 0, 0);
return;
case RAN_CONN_E_MO_CLOSE:
case RAN_CONN_E_CN_CLOSE:
log_close_event(fi, event, data);
/* fall through */
case RAN_CONN_E_UNUSED:
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static void ran_conn_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case RAN_CONN_E_COMMUNICATING:
/* no-op */
return;
case RAN_CONN_E_MO_CLOSE:
case RAN_CONN_E_CN_CLOSE:
log_close_event(fi, event, data);
/* fall through */
case RAN_CONN_E_UNUSED:
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASING, RAN_CONN_TIMEOUT, 0);
return;
default:
OSMO_ASSERT(false);
}
}
static int ran_conn_fsm_timeout(struct osmo_fsm_inst *fi)
{
struct ran_conn *conn = fi->priv;
if (ran_conn_in_release(conn)) {
LOGPFSML(fi, LOGL_ERROR, "Timeout while releasing, discarding right now\n");
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, NULL);
} else {
enum gsm48_reject_value cause = GSM48_REJECT_CONGESTION;
osmo_fsm_inst_dispatch(fi, RAN_CONN_E_CN_CLOSE, &cause);
}
return 0;
}
static void ran_conn_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct ran_conn *conn = fi->priv;
/* The SGs interface needs to access vsub struct members to send the
* release message, however the following release procedures will
* remove conn->vsub, so we need to send the release right now. */
if (conn->via_ran == OSMO_RAT_EUTRAN_SGS) {
sgs_iface_tx_release(conn);
}
/* Use count for either conn->a.waiting_for_clear_complete or
* conn->iu.waiting_for_release_complete. 'get' it early, so we don't deallocate after tearing
* down active transactions. Safeguard against double-get (though it shouldn't happen). */
if (!ran_conn_used_by(conn, RAN_CONN_USE_RELEASE))
ran_conn_get(conn, RAN_CONN_USE_RELEASE);
/* Cancel pending CM Service Requests */
if (conn->received_cm_service_request) {
conn->received_cm_service_request = false;
ran_conn_put(conn, RAN_CONN_USE_CM_SERVICE);
}
/* Cancel all VLR FSMs, if any */
vlr_subscr_cancel_attach_fsm(conn->vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION);
if (conn->vsub) {
/* The subscriber has no active connection anymore.
* Restart the periodic Location Update expiry timer for this subscriber. */
vlr_subscr_enable_expire_lu(conn->vsub);
}
/* If we're closing in a middle of a trans, we need to clean up */
trans_conn_closed(conn);
switch (conn->via_ran) {
case OSMO_RAT_GERAN_A:
a_iface_tx_clear_cmd(conn);
if (conn->a.waiting_for_clear_complete) {
LOGPFSML(fi, LOGL_ERROR,
"Unexpected: conn is already waiting for BSSMAP Clear Complete\n");
break; break;
}
conn->a.waiting_for_clear_complete = true;
break;
case OSMO_RAT_UTRAN_IU:
ranap_iu_tx_release(conn->iu.ue_ctx, NULL);
if (conn->iu.waiting_for_release_complete) {
LOGPFSML(fi, LOGL_ERROR,
"Unexpected: conn is already waiting for Iu Release Complete\n");
break;
}
conn->iu.waiting_for_release_complete = true;
break;
case OSMO_RAT_EUTRAN_SGS:
/* Release message is already sent at the beginning of this
* functions (see above), but we still need to notify the
* conn that a release has been sent / is in progress. */
ran_conn_sgs_release_sent(conn);
break;
default:
LOGP(DMM, LOGL_ERROR, "%s: Unknown RAN type, cannot tx release/clear\n",
vlr_subscr_name(conn->vsub));
break;
} }
if (already_used)
return NULL;
LOG_RAN_PEER(ran_peer, LOGL_DEBUG, "Outgoing conn id: %u\n", conn_id);
return ran_conn_create_incoming(ran_peer, conn_id);
} }
static void ran_conn_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
OSMO_ASSERT(event == RAN_CONN_E_UNUSED);
osmo_fsm_inst_state_chg(fi, RAN_CONN_S_RELEASED, 0, 0);
}
static void ran_conn_fsm_released(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* Terminate, deallocate and also deallocate the ran_conn, which is allocated as
* a talloc child of fi. Also calls the cleanup function. */
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state ran_conn_fsm_states[] = {
[RAN_CONN_S_NEW] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_NEW),
.in_event_mask = S(RAN_CONN_E_COMPLETE_LAYER_3) |
S(RAN_CONN_E_ACCEPTED) |
S(RAN_CONN_E_MO_CLOSE) |
S(RAN_CONN_E_CN_CLOSE) |
S(RAN_CONN_E_UNUSED),
.out_state_mask = S(RAN_CONN_S_AUTH_CIPH) |
S(RAN_CONN_S_ACCEPTED) |
S(RAN_CONN_S_RELEASING),
.action = ran_conn_fsm_new,
},
[RAN_CONN_S_AUTH_CIPH] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_AUTH_CIPH),
.in_event_mask = S(RAN_CONN_E_ACCEPTED) |
S(RAN_CONN_E_MO_CLOSE) |
S(RAN_CONN_E_CN_CLOSE) |
S(RAN_CONN_E_UNUSED),
.out_state_mask = S(RAN_CONN_S_WAIT_CLASSMARK_UPDATE) |
S(RAN_CONN_S_ACCEPTED) |
S(RAN_CONN_S_RELEASING),
.action = ran_conn_fsm_auth_ciph,
},
[RAN_CONN_S_WAIT_CLASSMARK_UPDATE] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_WAIT_CLASSMARK_UPDATE),
.in_event_mask = S(RAN_CONN_E_CLASSMARK_UPDATE) |
S(RAN_CONN_E_MO_CLOSE) |
S(RAN_CONN_E_CN_CLOSE) |
S(RAN_CONN_E_UNUSED),
.out_state_mask = S(RAN_CONN_S_AUTH_CIPH) |
S(RAN_CONN_S_RELEASING),
.action = ran_conn_fsm_wait_classmark_update,
},
[RAN_CONN_S_ACCEPTED] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_ACCEPTED),
/* allow everything to release for any odd behavior */
.in_event_mask = S(RAN_CONN_E_COMPLETE_LAYER_3) |
S(RAN_CONN_E_COMMUNICATING) |
S(RAN_CONN_E_RELEASE_WHEN_UNUSED) |
S(RAN_CONN_E_ACCEPTED) |
S(RAN_CONN_E_MO_CLOSE) |
S(RAN_CONN_E_CN_CLOSE) |
S(RAN_CONN_E_UNUSED),
.out_state_mask = S(RAN_CONN_S_RELEASING) |
S(RAN_CONN_S_COMMUNICATING),
.onenter = ran_conn_fsm_accepted_enter,
.action = ran_conn_fsm_accepted,
},
[RAN_CONN_S_COMMUNICATING] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_COMMUNICATING),
/* allow everything to release for any odd behavior */
.in_event_mask = S(RAN_CONN_E_RELEASE_WHEN_UNUSED) |
S(RAN_CONN_E_ACCEPTED) |
S(RAN_CONN_E_COMMUNICATING) |
S(RAN_CONN_E_MO_CLOSE) |
S(RAN_CONN_E_CN_CLOSE) |
S(RAN_CONN_E_UNUSED),
.out_state_mask = S(RAN_CONN_S_RELEASING),
.action = ran_conn_fsm_communicating,
},
[RAN_CONN_S_RELEASING] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_RELEASING),
.in_event_mask = S(RAN_CONN_E_UNUSED),
.out_state_mask = S(RAN_CONN_S_RELEASED),
.onenter = ran_conn_fsm_releasing_onenter,
.action = ran_conn_fsm_releasing,
},
[RAN_CONN_S_RELEASED] = {
.name = OSMO_STRINGIFY(RAN_CONN_S_RELEASED),
.onenter = ran_conn_fsm_released,
},
};
static void ran_conn_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause);
static struct osmo_fsm ran_conn_fsm = {
.name = "RAN_conn",
.states = ran_conn_fsm_states,
.num_states = ARRAY_SIZE(ran_conn_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.log_subsys = DMM,
.event_names = ran_conn_fsm_event_names,
.cleanup = ran_conn_fsm_cleanup,
.timer_cb = ran_conn_fsm_timeout,
};
/* Return statically allocated string of the ran_conn RAT type and id. */ /* Return statically allocated string of the ran_conn RAT type and id. */
const char *ran_conn_get_conn_id(struct ran_conn *conn) const char *ran_conn_name(struct ran_conn *conn)
{ {
static char id[42]; static char id[42];
int rc; int rc;
uint32_t conn_id; const char *ran_peer_name;
switch (conn->via_ran) { if (!conn)
case OSMO_RAT_GERAN_A: return "ran_conn==NULL";
conn_id = conn->a.conn_id;
break;
case OSMO_RAT_UTRAN_IU:
conn_id = iu_get_conn_id(conn->iu.ue_ctx);
break;
default:
return "ran-unknown";
}
rc = snprintf(id, sizeof(id), "%s-%u", osmo_rat_type_name(conn->via_ran), conn_id); if (!conn->ran_peer || !conn->ran_peer->sri || !conn->ran_peer->sri->ran)
ran_peer_name = "no-RAN-peer";
else
ran_peer_name = osmo_rat_type_name(conn->ran_peer->sri->ran->type);
rc = snprintf(id, sizeof(id), "%s-%u", ran_peer_name, conn->sccp_conn_id);
/* < 0 is error, == 0 is empty, >= size means truncation. Not really expecting this to catch on in any practical /* < 0 is error, == 0 is empty, >= size means truncation. Not really expecting this to catch on in any practical
* situation. */ * situation. */
if (rc <= 0 || rc >= sizeof(id)) { if (rc <= 0 || rc >= sizeof(id))
LOGP(DMM, LOGL_ERROR, "Error with conn id; rc=%d\n", rc); return "conn-name-error";
return "conn-id-error";
}
return id; return id;
} }
/* Tidy up before the FSM deallocates */ int ran_conn_down_l2_co(struct ran_conn *conn, struct msgb *l3, bool initial)
static void ran_conn_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{ {
struct ran_conn *conn = fi->priv; struct ran_peer_ev_ctx co = {
.conn_id = conn->sccp_conn_id,
if (ran_conn_fsm_has_active_transactions(fi)) { .conn = conn,
LOGPFSML(fi, LOGL_ERROR, "Deallocating despite active transactions\n"); .msg = l3,
trans_conn_closed(conn);
}
if (!conn) {
LOGP(DRLL, LOGL_ERROR, "Freeing NULL RAN connection\n");
return;
}
if (conn->vsub) {
DEBUGP(DRLL, "%s: Freeing RAN connection\n", vlr_subscr_name(conn->vsub));
conn->vsub->lu_fsm = NULL;
conn->vsub->msc_conn_ref = NULL;
vlr_subscr_put(conn->vsub, VSUB_USE_CONN);
conn->vsub = NULL;
} else
DEBUGP(DRLL, "Freeing RAN connection with NULL subscriber\n");
llist_del(&conn->entry);
}
/* Signal success of Complete Layer 3. Allow to keep the conn open for Auth and Ciph. */
void ran_conn_complete_layer_3(struct ran_conn *conn)
{
if (!conn)
return;
osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_COMPLETE_LAYER_3, NULL);
}
void ran_conn_release_when_unused(struct ran_conn *conn)
{
if (!conn)
return;
if (ran_conn_in_release(conn)) {
DEBUGP(DMM, "%s: %s: conn already in release (%s)\n",
vlr_subscr_name(conn->vsub), __func__,
osmo_fsm_inst_state_name(conn->fi));
return;
}
if (conn->fi->state == RAN_CONN_S_NEW) {
DEBUGP(DMM, "%s: %s: conn still being established (%s)\n",
vlr_subscr_name(conn->vsub), __func__,
osmo_fsm_inst_state_name(conn->fi));
return;
}
osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_RELEASE_WHEN_UNUSED, NULL);
}
static void conn_close(struct ran_conn *conn, uint32_t cause, uint32_t event)
{
if (!conn) {
LOGP(DMM, LOGL_ERROR, "Cannot release NULL connection\n");
return;
}
if (ran_conn_in_release(conn)) {
DEBUGP(DMM, "%s(vsub=%s, cause=%u): already in release, ignore.\n",
__func__, vlr_subscr_name(conn->vsub), cause);
return;
}
osmo_fsm_inst_dispatch(conn->fi, event, &cause);
}
void ran_conn_close(struct ran_conn *conn, uint32_t cause)
{
return conn_close(conn, cause, RAN_CONN_E_CN_CLOSE);
}
void ran_conn_mo_close(struct ran_conn *conn, uint32_t cause)
{
return conn_close(conn, cause, RAN_CONN_E_MO_CLOSE);
}
bool ran_conn_in_release(struct ran_conn *conn)
{
if (!conn || !conn->fi)
return true;
if (conn->fi->state == RAN_CONN_S_RELEASING)
return true;
if (conn->fi->state == RAN_CONN_S_RELEASED)
return true;
return false;
}
bool ran_conn_is_accepted(const struct ran_conn *conn)
{
if (!conn)
return false;
if (!conn->vsub)
return false;
if (!(conn->fi->state == RAN_CONN_S_ACCEPTED
|| conn->fi->state == RAN_CONN_S_COMMUNICATING))
return false;
return true;
}
/* Indicate that *some* communication is happening with the phone, so that the conn FSM no longer times
* out to release within a few seconds. */
void ran_conn_communicating(struct ran_conn *conn)
{
osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_COMMUNICATING, NULL);
}
void ran_conn_init(void)
{
osmo_fsm_register(&ran_conn_fsm);
}
/* Allocate a new RAN conn and FSM.
* Deallocation is by ran_conn_put(): when the use count reaches zero, the
* RAN_CONN_E_RELEASE_COMPLETE event is dispatched, the FSM terminates and deallocates both FSM and
* conn. As long as the FSM is waiting for responses from the subscriber, it will itself hold a use count
* on the conn. */
struct ran_conn *ran_conn_alloc(struct gsm_network *network,
enum osmo_rat_type via_ran, uint16_t lac)
{
struct ran_conn *conn;
struct osmo_fsm_inst *fi;
fi = osmo_fsm_inst_alloc(&ran_conn_fsm, network, NULL, LOGL_DEBUG, NULL);
if (!fi) {
LOGP(DMM, LOGL_ERROR, "Failed to allocate conn FSM\n");
return NULL;
}
conn = talloc_zero(fi, struct ran_conn);
if (!conn) {
osmo_fsm_inst_free(fi);
return NULL;
}
*conn = (struct ran_conn){
.network = network,
.via_ran = via_ran,
.lac = lac,
.fi = fi,
}; };
if (!conn->ran_peer)
switch (via_ran) { return -EIO;
case OSMO_RAT_GERAN_A: return osmo_fsm_inst_dispatch(conn->ran_peer->fi,
conn->log_subsys = DBSSAP; initial ? RAN_PEER_EV_MSG_DOWN_CO_INITIAL : RAN_PEER_EV_MSG_DOWN_CO,
break; &co);
case OSMO_RAT_UTRAN_IU:
conn->log_subsys = DRANAP;
break;
case OSMO_RAT_EUTRAN_SGS:
conn->log_subsys = DSGS;
break;
default:
conn->log_subsys = DMSC;
break;
}
fi->priv = conn;
llist_add_tail(&conn->entry, &network->ran_conns);
return conn;
} }
bool ran_conn_is_establishing_auth_ciph(const struct ran_conn *conn) void ran_conn_msc_role_gone(struct ran_conn *conn, struct osmo_fsm_inst *msc_role)
{
if (!conn)
return false;
return conn->fi->state == RAN_CONN_S_AUTH_CIPH;
}
const struct value_string complete_layer3_type_names[] = {
{ COMPLETE_LAYER3_NONE, "NONE" },
{ COMPLETE_LAYER3_LU, "LU" },
{ COMPLETE_LAYER3_CM_SERVICE_REQ, "CM_SERVICE_REQ" },
{ COMPLETE_LAYER3_PAGING_RESP, "PAGING_RESP" },
{ 0, NULL }
};
static void _ran_conn_update_id(struct ran_conn *conn, const char *subscr_identity)
{
struct vlr_subscr *vsub = conn->vsub;
if (osmo_fsm_inst_update_id_f(conn->fi, "%s:%s:%s",
subscr_identity,
ran_conn_get_conn_id(conn),
complete_layer3_type_name(conn->complete_layer3_type))
!= 0)
return; /* osmo_fsm_inst_update_id_f() will log an error. */
if (vsub) {
if (vsub->lu_fsm)
osmo_fsm_inst_update_id(vsub->lu_fsm, conn->fi->id);
if (vsub->auth_fsm)
osmo_fsm_inst_update_id(vsub->auth_fsm, conn->fi->id);
if (vsub->proc_arq_fsm)
osmo_fsm_inst_update_id(vsub->proc_arq_fsm, conn->fi->id);
}
LOGPFSML(conn->fi, LOGL_DEBUG, "Updated ID\n");
}
/* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */
void ran_conn_update_id_from_mi(struct ran_conn *conn, const uint8_t *mi, uint8_t mi_len)
{
_ran_conn_update_id(conn, osmo_mi_name(mi, mi_len));
}
/* Update ran_conn->fi id string from current conn->vsub and conn->complete_layer3_type. */
void ran_conn_update_id(struct ran_conn *conn)
{
_ran_conn_update_id(conn, vlr_subscr_name(conn->vsub));
}
/* Iterate all ran_conn instances that are relevant for this subscriber, and update FSM ID strings for all of the FSM
* instances. */
void ran_conn_update_id_for_vsub(struct vlr_subscr *for_vsub)
{
struct gsm_network *network;
struct ran_conn *conn;
if (!for_vsub)
return;
network = for_vsub->vlr->user_ctx;
OSMO_ASSERT(network);
llist_for_each_entry(conn, &network->ran_conns, entry) {
if (conn->vsub == for_vsub)
ran_conn_update_id(conn);
}
}
static void rx_close_complete(struct ran_conn *conn, const char *label, bool *flag)
{ {
if (!conn) if (!conn)
return; return;
if (!ran_conn_in_release(conn)) {
LOGPFSML(conn->fi, LOGL_ERROR, "Received unexpected %s, discarding right now\n", if (conn->msc_role != msc_role)
label);
trans_conn_closed(conn);
osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_ERROR, NULL);
return; return;
}
if (*flag) { conn->msc_role = NULL;
*flag = false; ran_conn_close(conn);
ran_conn_put(conn, RAN_CONN_USE_RELEASE);
}
} }
void ran_conn_rx_bssmap_clear_complete(struct ran_conn *conn) /* Regularly close the conn */
void ran_conn_close(struct ran_conn *conn)
{ {
rx_close_complete(conn, "BSSMAP Clear Complete", &conn->a.waiting_for_clear_complete); if (!conn)
}
void ran_conn_rx_iu_release_complete(struct ran_conn *conn)
{
rx_close_complete(conn, "Iu Release Complete", &conn->iu.waiting_for_release_complete);
}
void ran_conn_sgs_release_sent(struct ran_conn *conn)
{
bool dummy_waiting_for_release_complete = true;
/* Note: In SGsAP there is no confirmation of a release. */
rx_close_complete(conn, "SGs Release Complete", &dummy_waiting_for_release_complete);
}
const struct value_string ran_conn_use_names[] = {
{ RAN_CONN_USE_UNTRACKED, "UNTRACKED" },
{ RAN_CONN_USE_COMPL_L3, "compl_l3" },
{ RAN_CONN_USE_DTAP, "dtap" },
{ RAN_CONN_USE_AUTH_CIPH, "auth+ciph" },
{ RAN_CONN_USE_CM_SERVICE, "cm_service" },
{ RAN_CONN_USE_TRANS_CC, "trans_cc" },
{ RAN_CONN_USE_TRANS_SMS, "trans_sms" },
{ RAN_CONN_USE_TRANS_NC_SS, "trans_nc_ss" },
{ RAN_CONN_USE_SILENT_CALL, "silent_call" },
{ RAN_CONN_USE_RELEASE, "release" },
{ 0, NULL }
};
static const char *used_ref_counts_str(struct ran_conn *conn)
{
static char buf[256];
int bit_nr;
char *pos = buf;
*pos = '\0';
if (conn->use_tokens < 0)
return "invalid";
#define APPEND_STR(fmt, args...) do { \
int remain = sizeof(buf) - (pos - buf) - 1; \
int l = -1; \
if (remain > 0) \
l = snprintf(pos, remain, "%s" fmt, (pos == buf? "" : ","), ##args); \
if (l < 0 || l > remain) { \
buf[sizeof(buf) - 1] = '\0'; \
return buf; \
} \
pos += l; \
} while(0)
for (bit_nr = 0; (1 << bit_nr) <= conn->use_tokens; bit_nr++) {
if (conn->use_tokens & (1 << bit_nr)) {
APPEND_STR("%s", get_value_string(ran_conn_use_names, bit_nr));
}
}
return buf;
#undef APPEND_STR
}
/* increment the ref-count. Needs to be called by every user */
struct ran_conn *_ran_conn_get(struct ran_conn *conn, enum ran_conn_use balance_token,
const char *file, int line)
{
OSMO_ASSERT(conn);
if (balance_token != RAN_CONN_USE_UNTRACKED) {
uint32_t flag = 1 << balance_token;
OSMO_ASSERT(balance_token < 32);
if (conn->use_tokens & flag)
LOGPSRC(DREF, LOGL_ERROR, file, line,
"%s: MSC conn use error: using an already used token: %s\n",
vlr_subscr_name(conn->vsub),
ran_conn_use_name(balance_token));
conn->use_tokens |= flag;
}
conn->use_count++;
LOGPSRC(DREF, LOGL_DEBUG, file, line,
"%s: MSC conn use + %s == %u (0x%x: %s)\n",
vlr_subscr_name(conn->vsub), ran_conn_use_name(balance_token),
conn->use_count, conn->use_tokens, used_ref_counts_str(conn));
return conn;
}
/* decrement the ref-count. Once it reaches zero, we release */
void _ran_conn_put(struct ran_conn *conn, enum ran_conn_use balance_token,
const char *file, int line)
{
OSMO_ASSERT(conn);
if (balance_token != RAN_CONN_USE_UNTRACKED) {
uint32_t flag = 1 << balance_token;
OSMO_ASSERT(balance_token < 32);
if (!(conn->use_tokens & flag))
LOGPSRC(DREF, LOGL_ERROR, file, line,
"%s: MSC conn use error: freeing an unused token: %s\n",
vlr_subscr_name(conn->vsub),
ran_conn_use_name(balance_token));
conn->use_tokens &= ~flag;
}
if (conn->use_count == 0) {
LOGPSRC(DREF, LOGL_ERROR, file, line,
"%s: MSC conn use - %s failed: is already 0\n",
vlr_subscr_name(conn->vsub),
ran_conn_use_name(balance_token));
return; return;
if (conn->closing)
return;
conn->closing = true;
LOG_RAN_PEER(conn->ran_peer, LOGL_DEBUG, "Closing %s\n", ran_conn_name(conn));
if (conn->msc_role) {
osmo_fsm_inst_dispatch(conn->msc_role, MSC_EV_FROM_RAN_CONN_RELEASED, NULL);
conn->msc_role = NULL;
} }
conn->use_count--; if (conn->ran_peer) {
LOGPSRC(DREF, LOGL_DEBUG, file, line, /* Todo: pass a useful SCCP cause? */
"%s: MSC conn use - %s == %u (0x%x: %s)\n", sccp_ran_disconnect(conn->ran_peer->sri, conn->sccp_conn_id, 0);
vlr_subscr_name(conn->vsub), ran_conn_use_name(balance_token), conn->ran_peer = NULL;
conn->use_count, conn->use_tokens, used_ref_counts_str(conn)); }
if (conn->use_count == 0) LOG_RAN_PEER(conn->ran_peer, LOGL_DEBUG, "Deallocating %s\n", ran_conn_name(conn));
osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_UNUSED, NULL); llist_del(&conn->entry);
talloc_free(conn);
} }
bool ran_conn_used_by(struct ran_conn *conn, enum ran_conn_use token) /* Same as ran_conn_close() but without sending any SCCP messages (e.g. after RESET) */
void ran_conn_discard(struct ran_conn *conn)
{ {
return conn && (conn->use_tokens & (1 << token)); if (!conn)
return;
/* Make sure to drop dead and don't dispatch things like DISCONNECT requests on SCCP. */
conn->ran_peer = NULL;
ran_conn_close(conn);
} }

118
src/libmsc/ran_infra.c Normal file
View File

@ -0,0 +1,118 @@
/* Lookup table for various RAN implementations */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <osmocom/core/utils.h>
#include <osmocom/core/tdef.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/ran_msg_a.h>
#include <osmocom/msc/ran_msg_iu.h>
#include <osmocom/msc/ran_peer.h>
#include <osmocom/msc/ran_infra.h>
#include "bscconfig.h"
const struct value_string an_proto_names[] = {
{ OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006, "Ts3G-48006" },
{ OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413, "Ts3G-25413" },
{}
};
#define RAN_TDEFS \
{ .T = -1, .default_val = 5, .desc = "RAN connection Complete Layer 3, Authentication and Ciphering timeout" }, \
{ .T = -2, .default_val = 30, .desc = "RAN connection release sanity timeout" }, \
{ .T = -3, .default_val = 10, .desc = "Timeout to find a target BSS after Handover Required" }, \
struct osmo_tdef msc_tdefs_geran[] = {
RAN_TDEFS
{}
};
struct osmo_tdef msc_tdefs_utran[] = {
RAN_TDEFS
{}
};
struct osmo_tdef msc_tdefs_sgs[] = {
{}
};
static __attribute__((constructor)) void ran_infra_init()
{
osmo_tdefs_reset(msc_tdefs_geran);
osmo_tdefs_reset(msc_tdefs_utran);
osmo_tdefs_reset(msc_tdefs_sgs);
}
struct ran_infra msc_ran_infra[] = {
[OSMO_RAT_UNKNOWN] = {
.type = OSMO_RAT_UNKNOWN,
.log_subsys = DMSC,
.tdefs = msc_tdefs_geran,
},
[OSMO_RAT_GERAN_A] = {
.type = OSMO_RAT_GERAN_A,
.an_proto = OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006,
.ssn = OSMO_SCCP_SSN_BSSAP,
.log_subsys = DBSSAP,
.tdefs = msc_tdefs_geran,
.sccp_ran_ops = {
.up_l2 = ran_peer_up_l2,
.disconnect = ran_peer_disconnect,
.is_reset_msg = bssmap_is_reset_msg,
.make_reset_msg = bssmap_make_reset_msg,
.make_paging_msg = bssmap_make_paging_msg,
.msg_name = bssmap_msg_name,
},
.ran_dec_l2 = ran_a_decode_l2,
.ran_encode = ran_a_encode,
},
[OSMO_RAT_UTRAN_IU] = {
.type = OSMO_RAT_UTRAN_IU,
.an_proto = OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413,
.ssn = OSMO_SCCP_SSN_RANAP,
.log_subsys = DIUCS,
.tdefs = msc_tdefs_utran,
#if BUILD_IU
.sccp_ran_ops = {
.up_l2 = ran_peer_up_l2,
.disconnect = ran_peer_disconnect,
.is_reset_msg = ranap_is_reset_msg,
.make_reset_msg = ranap_make_reset_msg,
.make_paging_msg = ranap_make_paging_msg,
.msg_name = ranap_msg_name,
},
.ran_dec_l2 = ran_iu_decode_l2,
.ran_encode = ran_iu_encode,
#endif
},
[OSMO_RAT_EUTRAN_SGS] = {
.type = OSMO_RAT_EUTRAN_SGS,
.log_subsys = DSGS,
.ran_encode = NULL,
.tdefs = msc_tdefs_sgs,
},
};
const int msc_ran_infra_len = ARRAY_SIZE(msc_ran_infra);

160
src/libmsc/ran_msg.c Normal file
View File

@ -0,0 +1,160 @@
/* Common bits for RAN message handling */
/*
* (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Neels Hofmeyr
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/utils.h>
#include <osmocom/msc/ran_msg.h>
const struct value_string ran_msg_type_names[] = {
{ RAN_MSG_NONE, "NONE" },
{ RAN_MSG_COMPL_L3, "COMPL_L3" },
{ RAN_MSG_DTAP, "DTAP" },
{ RAN_MSG_CLEAR_COMMAND, "CLEAR_COMMAND" },
{ RAN_MSG_CLEAR_REQUEST, "CLEAR_REQUEST" },
{ RAN_MSG_CLEAR_COMPLETE, "CLEAR_COMPLETE" },
{ RAN_MSG_CLASSMARK_REQUEST, "CLASSMARK_REQUEST" },
{ RAN_MSG_CLASSMARK_UPDATE, "CLASSMARK_UPDATE" },
{ RAN_MSG_CIPHER_MODE_COMMAND, "CIPHER_MODE_COMMAND" },
{ RAN_MSG_CIPHER_MODE_COMPLETE, "CIPHER_MODE_COMPLETE" },
{ RAN_MSG_CIPHER_MODE_REJECT, "CIPHER_MODE_REJECT" },
{ RAN_MSG_COMMON_ID, "COMMON_ID" },
{ RAN_MSG_ASSIGNMENT_COMMAND, "ASSIGNMENT_COMMAND" },
{ RAN_MSG_ASSIGNMENT_COMPLETE, "ASSIGNMENT_COMPLETE" },
{ RAN_MSG_ASSIGNMENT_FAILURE, "ASSIGNMENT_FAILURE" },
{ RAN_MSG_SAPI_N_REJECT, "SAPI_N_REJECT" },
{ RAN_MSG_LCLS_STATUS, "LCLS_STATUS" },
{ RAN_MSG_LCLS_BREAK_REQ, "LCLS_BREAK_REQ" },
{ RAN_MSG_HANDOVER_COMMAND, "HANDOVER_COMMAND" },
{ RAN_MSG_HANDOVER_SUCCEEDED, "HANDOVER_SUCCEEDED" },
{ RAN_MSG_HANDOVER_PERFORMED, "HANDOVER_PERFORMED" },
{ RAN_MSG_HANDOVER_REQUIRED, "HANDOVER_REQUIRED" },
{ RAN_MSG_HANDOVER_REQUIRED_REJECT, "HANDOVER_REQUIRED_REJECT" },
{ RAN_MSG_HANDOVER_REQUEST, "HANDOVER_REQUEST" },
{ RAN_MSG_HANDOVER_REQUEST_ACK, "HANDOVER_REQUEST_ACK" },
{ RAN_MSG_HANDOVER_DETECT, "HANDOVER_DETECT" },
{ RAN_MSG_HANDOVER_COMPLETE, "HANDOVER_COMPLETE" },
{ RAN_MSG_HANDOVER_FAILURE, "HANDOVER_FAILURE" },
{}
};
/* extract the N(SD) and return the modulo value for a R99 message */
static uint8_t ran_dec_dtap_undup_determine_nsd_ret_modulo_r99(uint8_t pdisc, uint8_t msg_type, uint8_t *n_sd)
{
switch (pdisc) {
case GSM48_PDISC_MM:
case GSM48_PDISC_CC:
case GSM48_PDISC_NC_SS:
*n_sd = (msg_type >> 6) & 0x3;
return 4;
case GSM48_PDISC_GROUP_CC:
case GSM48_PDISC_BCAST_CC:
case GSM48_PDISC_LOC:
*n_sd = (msg_type >> 6) & 0x1;
return 2;
default:
/* no sequence number, we cannot detect dups */
return 0;
}
}
/* extract the N(SD) and return the modulo value for a R98 message */
static uint8_t gsm0407_determine_nsd_ret_modulo_r98(uint8_t pdisc, uint8_t msg_type, uint8_t *n_sd)
{
switch (pdisc) {
case GSM48_PDISC_MM:
case GSM48_PDISC_CC:
case GSM48_PDISC_NC_SS:
case GSM48_PDISC_GROUP_CC:
case GSM48_PDISC_BCAST_CC:
case GSM48_PDISC_LOC:
*n_sd = (msg_type >> 6) & 0x1;
return 2;
default:
/* no sequence number, we cannot detect dups */
return 0;
}
}
/* TS 24.007 11.2.3.2.3 Message Type Octet / Duplicate Detection.
* (Not static for unit testing). */
int ran_dec_dtap_undup_pdisc_ctr_bin(uint8_t pdisc)
{
switch (pdisc) {
case GSM48_PDISC_MM:
case GSM48_PDISC_CC:
case GSM48_PDISC_NC_SS:
return 0;
case GSM48_PDISC_GROUP_CC:
return 1;
case GSM48_PDISC_BCAST_CC:
return 2;
case GSM48_PDISC_LOC:
return 3;
default:
return -1;
}
}
/* TS 24.007 11.2.3.2 Message Type Octet / Duplicate Detection */
bool ran_dec_dtap_undup_is_duplicate(struct osmo_fsm_inst *log_fi, uint8_t *n_sd_next, bool is_r99, struct msgb *l3)
{
struct gsm48_hdr *gh;
uint8_t pdisc;
uint8_t n_sd, modulo;
int bin;
gh = msgb_l3(l3);
pdisc = gsm48_hdr_pdisc(gh);
if (is_r99) {
modulo = ran_dec_dtap_undup_determine_nsd_ret_modulo_r99(pdisc, gh->msg_type, &n_sd);
} else { /* pre R99 */
modulo = gsm0407_determine_nsd_ret_modulo_r98(pdisc, gh->msg_type, &n_sd);
}
if (modulo == 0)
return false;
bin = ran_dec_dtap_undup_pdisc_ctr_bin(pdisc);
if (bin < 0)
return false;
OSMO_ASSERT(bin >= 0 && bin < 4);
if (n_sd != n_sd_next[bin]) {
/* not what we expected: duplicate */
LOGPFSML(log_fi, LOGL_NOTICE, "Duplicate DTAP: bin=%d, expected n_sd == %u, got %u\n",
bin, n_sd_next[bin], n_sd);
return true;
} else {
/* as expected: no dup; update expected counter for next message */
n_sd_next[bin] = (n_sd + 1) % modulo;
return false;
}
}
/* convenience: RAN decode implementations can call this to dispatch the decode_cb with a decoded ran_msg. */
int ran_decoded(struct ran_dec *ran_dec, struct ran_msg *ran_msg)
{
if (!ran_dec->decode_cb)
return -1;
return ran_dec->decode_cb(ran_dec->caller_fi, ran_dec->caller_data, ran_msg);
}

1284
src/libmsc/ran_msg_a.c Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More