From 3561bd48976dbee8dbd4659dad15be85a3e79ace Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 28 Jan 2018 03:04:16 +0100 Subject: introduce an osmo_fsm for gsm_subscriber_connection In the current implementation of osmo-bsc, the subscriber connection is not handled (very) statefully. However, there is some state keeping in the code that handles the mgcp connection, but there are still to much loose ends which allow odd situations to happen, which then lead severe error situations (see also closes tags at the end) This commit adds a number of improvements to fix those problems. - Use an osmo-fsm to control the gsm_subscriber_connection state and make sure that certain operations can only take place at certain states (e.g let connection oriented SCCP traffic only pass when an SCCP connection actually exists. Remove the old osmo_bsc_mgcp.c code. Use the recently developed MGCP client FSM to handle the MGCP connections. Also make sure that stuff that already works does not break. This in particular refers to the internal handover capability and the respective unit-tests. See also OS#2823, OS#2768 and OS#2898 - Fix logic to permit assignment to a signalling channel. (OS#2762) - Introduce T993210 to release lchan + subscr_conn if MSC fails to respond The GSM specs don't have an explicit timer for this, so let's introdcue a custom timer (hence starting with 99). This timeout catches the following situation: * we send a SCCP CR with COMPL_L3_INFO from the MS to the MSC, * the MSC doesn't respond (e.g. SCCP routing failure, program down, ...) The MS is supposed to timeout with T3210, 3220 or 3230. But the BSC shouldn't trust the MS but have some timer on its own. SCCP would have a timer T(conn est), but that one is specified to be 1-2min and hence rather long. See also: OS#2775 - Terminate bsc_subscr_conn_fsm on SCCP N-DISC.ind from MSC If the MSC is disconnecting the SCCP channel, we must terminate the FSM which in turn will release all lchan's and other state. This makes TC_chan_rel_hard_rlsd pass, see also OS#2731 As a side-effect, this fixes TC_chan_act_ack_est_ind_refused(), where the MSC is answering with CREF to our CR/COMPL_L3. - Release subscriber connection on RLL RELEASE IND of SAPI0 on main DCCH The subscriber connection isn't really useful for anything after the SAPI0 main signalling link has been released. We could try to re-establish, but our best option is probably simply releasing the subscriber_conn and anything related to it. This will make TC_chan_rel_rll_rel_ind pass, see also OS#2730 This commit has been tested using the BSC_Tests TTCN3 testsuit and the following tests were passed: TC_chan_act_noreply TC_chan_act_ack_noest TC_chan_act_ack_est_ind_noreply TC_chan_act_ack_est_ind_refused TC_chan_act_nack TC_chan_exhaustion TC_ctrl TC_chan_rel_conn_fail TC_chan_rel_hard_clear TC_chan_rel_hard_rlsd TC_chan_rel_a_reset TC_rll_est_ind_inact_lchan TC_rll_est_ind_inval_sapi1 TC_rll_est_ind_inval_sapi3 TC_rll_est_ind_inval_sacch TC_assignment_cic_only TC_assignment_csd TC_assignment_ctm TC_assignment_fr_a5_0 TC_assignment_fr_a5_1_codec_missing TC_assignment_fr_a5_1 TC_assignment_fr_a5_3 TC_assignment_fr_a5_4 TC_paging_imsi_nochan TC_paging_tmsi_nochan TC_paging_tmsi_any TC_paging_tmsi_sdcch TC_paging_tmsi_tch_f TC_paging_tmsi_tch_hf TC_paging_imsi_nochan_cgi TC_paging_imsi_nochan_lac_ci TC_paging_imsi_nochan_ci TC_paging_imsi_nochan_lai TC_paging_imsi_nochan_lac TC_paging_imsi_nochan_all TC_paging_imsi_nochan_plmn_lac_rnc TC_paging_imsi_nochan_rnc TC_paging_imsi_nochan_lac_rnc TC_paging_imsi_nochan_lacs TC_paging_imsi_nochan_lacs_empty TC_paging_imsi_a_reset TC_paging_counter TC_rsl_drop_counter TC_classmark TC_unsol_ass_fail TC_unsol_ass_compl TC_unsol_ho_fail TC_err_82_short_msg TC_ho_int Authors: Harald Welte Philipp Maier Neels Hofmeyr Closes: OS#2730 Closes: OS#2731 Closes: OS#2762 Closes: OS#2768 Closes: OS#2775 Closes: OS#2823 Closes: OS#2898 Closes: OS#2936 Change-Id: I68286d26e2014048b054f39ef29c35fef420cc97 --- src/ipaccess/Makefile.am | 1 - src/libbsc/Makefile.am | 2 + src/libbsc/abis_rsl.c | 19 +- src/libbsc/bsc_api.c | 110 +--- src/libbsc/bsc_subscr_conn_fsm.c | 1056 +++++++++++++++++++++++++++++++++++ src/libbsc/handover_decision_2.c | 6 +- src/libbsc/handover_logic.c | 170 +++--- src/osmo-bsc/Makefile.am | 1 - src/osmo-bsc/osmo_bsc_api.c | 115 ++-- src/osmo-bsc/osmo_bsc_audio.c | 18 +- src/osmo-bsc/osmo_bsc_bssap.c | 280 ++++------ src/osmo-bsc/osmo_bsc_main.c | 5 +- src/osmo-bsc/osmo_bsc_mgcp.c | 1149 -------------------------------------- src/osmo-bsc/osmo_bsc_sigtran.c | 107 ++-- src/utils/Makefile.am | 1 - 15 files changed, 1408 insertions(+), 1632 deletions(-) create mode 100644 src/libbsc/bsc_subscr_conn_fsm.c delete mode 100644 src/osmo-bsc/osmo_bsc_mgcp.c (limited to 'src') diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am index a6195b9c7..9b2a83e31 100644 --- a/src/ipaccess/Makefile.am +++ b/src/ipaccess/Makefile.am @@ -24,7 +24,6 @@ OSMO_LIBS = \ bin_PROGRAMS = \ abisip-find \ - ipaccess-config \ ipaccess-proxy \ $(NULL) diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am index 805a7ee1f..895636387 100644 --- a/src/libbsc/Makefile.am +++ b/src/libbsc/Makefile.am @@ -13,6 +13,7 @@ AM_CFLAGS = \ $(LIBOSMOMGCP_CFLAGS) \ $(LIBOSMOSIGTRAN_CFLAGS) \ $(COVERAGE_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(NULL) noinst_LIBRARIES = \ @@ -62,5 +63,6 @@ libbsc_a_SOURCES = \ handover_cfg.c \ penalty_timers.c \ handover_decision_2.c \ + bsc_subscr_conn_fsm.c \ $(NULL) diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c index 7400f896e..2017d2c0c 100644 --- a/src/libbsc/abis_rsl.c +++ b/src/libbsc/abis_rsl.c @@ -45,6 +45,7 @@ #include #include #include +#include #define RSL_ALLOC_SIZE 1024 #define RSL_ALLOC_HEADROOM 128 @@ -1357,21 +1358,28 @@ static int rsl_rx_chan_act_nack(struct msgb *msg) static int rsl_rx_conn_fail(struct msgb *msg) { struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; struct tlv_parsed tp; + uint8_t cause = 0; - LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING state %s ", + LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL in state %s ", gsm_lchan_name(msg->lchan), gsm_lchans_name(msg->lchan->state)); rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); - if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE), TLVP_LEN(&tp, RSL_IE_CAUSE)); + cause = *TLVP_VAL(&tp, RSL_IE_CAUSE); + } LOGPC(DRSL, LOGL_NOTICE, "\n"); - rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]); - return rsl_rf_chan_release_err(msg->lchan); + rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]); + + osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, &cause); + + return 0; } static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru, @@ -2232,6 +2240,9 @@ static int abis_rsl_rx_rll(struct msgb *msg) rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_REL_IND); rsl_handle_release(msg->lchan); + /* if it was the main signalling link, let the subscr_conn_fsm know */ + if (msg->lchan->conn && sapi == 0 && (rllh->link_id >> 6) == 0) + osmo_fsm_inst_dispatch(msg->lchan->conn->fi, GSCON_EV_RLL_REL_IND, msg); break; case RSL_MT_REL_CONF: /* BTS informs us of having received UA from MS, diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c index d792b5899..c47654778 100644 --- a/src/libbsc/bsc_api.c +++ b/src/libbsc/bsc_api.c @@ -2,7 +2,7 @@ /* (C) 2010-2011 by Holger Hans Peter Freyther * (C) 2010-2011 by On-Waves - * (C) 2009 by Harald Welte + * (C) 2009,2017 by Harald Welte * * All Rights Reserved * @@ -51,7 +51,7 @@ static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_a static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); /* GSM 08.08 3.2.2.33 */ -static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) +uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) { uint8_t channel_mode = 0, channel = 0; @@ -100,7 +100,7 @@ static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) return channel_mode << 4 | channel; } -static uint8_t chan_mode_to_speech(struct gsm_lchan *lchan) +uint8_t chan_mode_to_speech(struct gsm_lchan *lchan) { int mode = 0; @@ -133,27 +133,6 @@ static uint8_t chan_mode_to_speech(struct gsm_lchan *lchan) return mode; } -static void assignment_t10_timeout(void *_conn) -{ - struct bsc_api *api; - struct gsm_subscriber_connection *conn = - (struct gsm_subscriber_connection *) _conn; - - LOGP(DMSC, LOGL_ERROR, "Assignment T10 timeout on %p\n", conn); - - /* - * normal release on the secondary channel but only if the - * secondary_channel has not been released by the handle_chan_nack. - */ - if (conn->secondary_lchan) - lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); - conn->secondary_lchan = NULL; - - /* inform them about the failure */ - api = conn->network->bsc_api; - api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); -} - /*! \brief Determine and apply AMR multi-rate configuration to lchan * Determine which AMR multi-rate configuration to use and apply it to * the lchan (so it can be communicated to BTS and MS during channel @@ -265,24 +244,6 @@ static int handle_new_assignment(struct gsm_subscriber_connection *conn, int cha return 0; } -struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lchan) -{ - struct gsm_subscriber_connection *conn; - struct gsm_network *net = lchan->ts->trx->bts->network; - - conn = talloc_zero(net, struct gsm_subscriber_connection); - if (!conn) - return NULL; - - conn->network = net; - conn->lchan = lchan; - lchan->conn = conn; - INIT_LLIST_HEAD(&conn->ho_dtap_cache); - conn->sccp.conn_id = -1; - llist_add_tail(&conn->entry, &net->subscr_conns); - return conn; -} - static void ho_dtap_cache_add(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id, bool allow_sacch) { @@ -301,12 +262,12 @@ static void ho_dtap_cache_add(struct gsm_subscriber_connection *conn, struct msg msgb_enqueue(&conn->ho_dtap_cache, msg); } -static void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send) +void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send) { struct msgb *msg; unsigned int flushed_count = 0; - if (conn->secondary_lchan || conn->ho_lchan) { + if (conn->secondary_lchan || conn->ho) { LOGP(DHO, LOGL_ERROR, "%s: Cannot send cached DTAP messages, handover/assignment is still ongoing\n", bsc_subscr_name(conn->bsub)); send = 0; @@ -326,38 +287,6 @@ static void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send } } -void bsc_subscr_con_free(struct gsm_subscriber_connection *conn) -{ - if (!conn) - return; - - if (conn->network->bsc_api->conn_cleanup) - conn->network->bsc_api->conn_cleanup(conn); - - if (conn->ho_lchan) { - LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n"); - conn->ho_lchan->conn = NULL; - } - - if (conn->lchan) { - LOGP(DNM, LOGL_ERROR, "The lchan should have been cleared.\n"); - conn->lchan->conn = NULL; - } - - if (conn->secondary_lchan) { - LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n"); - conn->secondary_lchan->conn = NULL; - } - - /* drop pending messages */ - ho_dtap_cache_flush(conn, 0); - - penalty_timers_free(&conn->hodec2.penalty_timers); - - llist_del(&conn->entry); - talloc_free(conn); -} - int bsc_api_init(struct gsm_network *network, struct bsc_api *api) { network->bsc_api = api; @@ -379,7 +308,7 @@ int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, } /* buffer message during assignment / handover */ - if (conn->secondary_lchan || conn->ho_lchan) { + if (conn->secondary_lchan || conn->ho) { ho_dtap_cache_add(conn, msg, link_id, !! allow_sacch); return 0; } @@ -478,9 +407,7 @@ int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, in gsm48_lchan_modify(conn->lchan, chan_mode); } - /* we will now start the timer to complete the assignment */ - osmo_timer_setup(&conn->T10, assignment_t10_timeout, conn); - osmo_timer_schedule(&conn->T10, GSM0808_T10_VALUE); + /* we expect the caller will manage T10 */ return 0; error: @@ -500,7 +427,7 @@ static void handle_ass_compl(struct gsm_subscriber_connection *conn, struct gsm48_hdr *gh; struct bsc_api *api = conn->network->bsc_api; - if (conn->ho_lchan) { + if (conn->ho) { struct lchan_signal_data sig; struct gsm48_hdr *gh = msgb_l3(msg); @@ -556,7 +483,7 @@ static void handle_ass_fail(struct gsm_subscriber_connection *conn, uint8_t *rr_failure; struct gsm48_hdr *gh; - if (conn->ho_lchan) { + if (conn->ho) { struct lchan_signal_data sig; struct gsm48_hdr *gh = msgb_l3(msg); @@ -796,19 +723,18 @@ int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id) } else { /* allocate a new connection */ rc = BSC_API_CONN_POL_REJECT; - lchan->conn = bsc_subscr_con_allocate(msg->lchan); + lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network); if (!lchan->conn) { lchan_release(lchan, 1, RSL_REL_NORMAL); return -1; } + lchan->conn->lchan = lchan; /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */ rc = api->compl_l3(lchan->conn, msg, 0); if (rc != BSC_API_CONN_POL_ACCEPT) { - lchan->conn->lchan = NULL; - bsc_subscr_con_free(lchan->conn); - lchan_release(lchan, 1, RSL_REL_NORMAL); + //osmo_fsm_inst_dispatch(lchan->conn->fi, FIXME, NULL); } } @@ -846,7 +772,7 @@ int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, */ int gsm0808_clear(struct gsm_subscriber_connection *conn) { - if (conn->ho_lchan) + if (conn->ho) bsc_clear_handover(conn, 1); if (conn->secondary_lchan) @@ -857,7 +783,6 @@ int gsm0808_clear(struct gsm_subscriber_connection *conn) conn->lchan = NULL; conn->secondary_lchan = NULL; - conn->ho_lchan = NULL; osmo_timer_del(&conn->T10); @@ -959,16 +884,9 @@ static void handle_release(struct gsm_subscriber_connection *conn, /* now give up all channels */ if (conn->lchan == lchan) conn->lchan = NULL; - if (conn->ho_lchan == lchan) { + if (conn->ho && conn->ho->new_lchan == lchan) bsc_clear_handover(conn, 0); - conn->ho_lchan = NULL; - } lchan->conn = NULL; - - gsm0808_clear(conn); - - if (destruct) - bsc_subscr_con_free(conn); } static void handle_chan_ack(struct gsm_subscriber_connection *conn, diff --git a/src/libbsc/bsc_subscr_conn_fsm.c b/src/libbsc/bsc_subscr_conn_fsm.c new file mode 100644 index 000000000..652fdcdec --- /dev/null +++ b/src/libbsc/bsc_subscr_conn_fsm.c @@ -0,0 +1,1056 @@ +/* (C) 2017 by Harald Welte + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S(x) (1 << (x)) + +#define MGCP_MGW_TIMEOUT 4 /* in seconds */ +#define MGCP_MGW_TIMEOUT_TIMER_NR 1 + +#define MGCP_MGW_HO_TIMEOUT 4 /* in seconds */ +#define MGCP_MGW_HO_TIMEOUT_TIMER_NR 2 + +#define GSM0808_T10_TIMER_NR 10 +#define GSM0808_T10_VALUE 6 + +#define ENDPOINT_ID "rtpbridge/*@mgw" + +enum gscon_fsm_states { + ST_INIT, + /* waiting for CC from MSC */ + ST_WAIT_CC, + /* active connection */ + ST_ACTIVE, + /* during assignment; waiting for ASS_CMPL */ + ST_WAIT_ASS_CMPL, + /* during assignment; waiting for MODE_MODIFY_ACK */ + ST_WAIT_MODE_MODIFY_ACK, + /* BSSMAP CLEAR has been received */ + ST_CLEARING, + +/* MGW handling */ + /* during assignment; waiting for MGW response to CRCX for BTS */ + ST_WAIT_CRCX_BTS, + /* during assignment; waiting for MGW response to MDCX for BTS */ + ST_WAIT_MDCX_BTS, + /* during assignment; waiting for MGW response to CRCX for MSC */ + ST_WAIT_CRCX_MSC, + +/* MT (inbound) handover */ + /* Wait for Handover Access from MS/BTS */ + ST_WAIT_MT_HO_ACC, + /* Wait for RR Handover Complete from MS/BTS */ + ST_WAIT_MT_HO_COMPL, + +/* MO (outbound) handover */ + /* Wait for Handover Command / Handover Required Reject from MSC */ + ST_WAIT_MO_HO_CMD, + /* Wait for Clear Command from MSC */ + ST_MO_HO_PROCEEDING, + +/* Internal HO handling */ + /* Wait for the handover logic to complete the handover */ + ST_WAIT_HO_COMPL, + /* during handover; waiting for MGW response to MDCX for BTS */ + ST_WAIT_MDCX_BTS_HO, +}; + +static const struct value_string gscon_fsm_event_names[] = { + {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, + {GSCON_EV_A_CONN_REQ, "MO-CONNECT.req"}, + {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, + {GSCON_EV_A_ASSIGNMENT_CMD, "ASSIGNMENT_CMD"}, + {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, + {GSCON_EV_A_DISC_IND, "DISCONNET.ind"}, + {GSCON_EV_A_HO_REQ, "HANDOVER_REQUEST"}, + + {GSCON_EV_RR_ASS_COMPL, "RR_ASSIGN_COMPL"}, + {GSCON_EV_RR_ASS_FAIL, "RR_ASSIGN_FAIL"}, + {GSCON_EV_RR_MODE_MODIFY_ACK, "RR_MODE_MODIFY_ACK"}, + {GSCON_EV_RR_HO_ACC, "RR_HO_ACCESS"}, + {GSCON_EV_RR_HO_COMPL, "RR_HO_COMPLETE"}, + {GSCON_EV_RLL_REL_IND, "RLL_RELEASE.ind"}, + {GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL.ind"}, + {GSCON_EV_RSL_CLEAR_COMPL, "RSL_CLEAR_COMPLETE"}, + + {GSCON_EV_MO_DTAP, "MO-DTAP"}, + {GSCON_EV_MT_DTAP, "MT-DTAP"}, + {GSCON_EV_TX_SCCP, "TX_SCCP"}, + + {GSCON_EV_MGW_FAIL_BTS, "MGW_FAILURE_BTS"}, + {GSCON_EV_MGW_FAIL_MSC, "MGW_FAILURE_MSC"}, + {GSCON_EV_MGW_CRCX_RESP_BTS, "MGW_CRCX_RESPONSE_BTS"}, + {GSCON_EV_MGW_MDCX_RESP_BTS, "MGW_MDCX_RESPONSE_BTS"}, + {GSCON_EV_MGW_CRCX_RESP_MSC, "MGW_CRCX_RESPONSE_MSC"}, + + {GSCON_EV_HO_START, "HO_START"}, + {GSCON_EV_HO_TIMEOUT, "HO_TIMEOUT"}, + {GSCON_EV_HO_FAIL, "HO_FAIL"}, + {GSCON_EV_HO_COMPL, "HO_COMPL"}, + + {0, NULL} +}; + +/* Send data SCCP message through SCCP connection. All sigtran messages + * that are send from this FSM must use this function. Never use + * osmo_bsc_sigtran_send() directly since this would defeat the checks + * provided by this function. */ +static void sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + int rc; + + /* Make sure that we only attempt to send SCCP messages if we have + * a life SCCP connection. Otherwise drop the message. */ + if (fi->state == ST_INIT || fi->state == ST_WAIT_CC) { + LOGPFSML(fi, LOGL_ERROR, "No active SCCP connection, dropping message!\n"); + msgb_free(msg); + return; + } + + rc = osmo_bsc_sigtran_send(conn, msg); + if (rc < 0) + LOGPFSML(fi, LOGL_ERROR, "Unable to deliver SCCP message!\n"); +} + +/* Generate and send assignment complete message */ +static void send_ass_compl(struct gsm_lchan *lchan, struct osmo_fsm_inst *fi) +{ + struct msgb *resp; + struct gsm0808_speech_codec sc; + struct gsm_subscriber_connection *conn; + + conn = lchan->conn; + + OSMO_ASSERT(lchan->abis_ip.ass_compl.valid); + OSMO_ASSERT(conn); + + LOGPFSML(fi, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.conn_id); + + /* Extrapolate speech codec from speech mode */ + gsm0808_speech_codec_from_chan_type(&sc, lchan->abis_ip.ass_compl.speech_mode); + + /* Generate message */ + resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, + lchan->abis_ip.ass_compl.chosen_channel, + lchan->abis_ip.ass_compl.encr_alg_id, + lchan->abis_ip.ass_compl.speech_mode, + &conn->user_plane.aoip_rtp_addr_local, &sc, NULL); + + if (!resp) { + LOGPFSML(fi, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n", + conn->sccp.conn_id); + } + + sigtran_send(conn, resp, fi); +} + +/* forward MT DTAP from BSSAP side to RSL side */ +static void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + int rc; + struct msgb *resp = NULL; + + OSMO_ASSERT(fi); + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + rc = gsm0808_submit_dtap(conn, msg, OBSC_LINKID_CB(msg), 1); + if (rc != 0) { + LOGPFSML(fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } +} + +/* forward MO DTAP from RSL side to BSSAP side */ +/* FIXME: move fi parameter to the beginning */ +static void forward_dtap(struct msgb *msg, struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi) +{ + struct msgb *resp = NULL; + + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); + sigtran_send(conn, resp, fi); +} + +/* In case there are open MGCP connections, toss + * those connections */ +static void toss_mgcp_conn(struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi) +{ + LOGPFSML(fi, LOGL_ERROR, "tossing all MGCP connections...\n"); + + if (conn->user_plane.fi_bts) { + mgcp_conn_delete(conn->user_plane.fi_bts); + conn->user_plane.fi_bts = NULL; + } + + if (conn->user_plane.fi_msc) { + mgcp_conn_delete(conn->user_plane.fi_msc); + conn->user_plane.fi_msc = NULL; + } + + if (conn->user_plane.mgw_endpoint) { + talloc_free(conn->user_plane.mgw_endpoint); + conn->user_plane.mgw_endpoint = NULL; + } +} + +static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct osmo_scu_prim *scu_prim = NULL; + struct msgb *msg = NULL; + int rc; + + switch (event) { + case GSCON_EV_A_CONN_REQ: + /* RLL ESTABLISH IND with initial L3 Message */ + msg = data; + /* FIXME: Extract Mobile ID and update FSM using osmo_fsm_inst_set_id() + * i.e. we will probably extract the mobile identity earlier, where the + * imsi filter code is. Then we could just use it here. + * related: OS#2969 */ + + rc = osmo_bsc_sigtran_open_conn(conn, msg); + if (rc < 0) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + } else { + /* SCCP T(conn est) is 1-2 minutes, way too long. The MS will timeout + * using T3210 (20s), T3220 (5s) or T3230 (10s) */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CC, 20, 993210); + } + break; + case GSCON_EV_A_CONN_IND: + scu_prim = data; + if (!conn->sccp.msc) { + LOGPFSML(fi, LOGL_NOTICE, "N-CONNECT.ind from unknown MSC %s\n", + osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr)); + osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, 0); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + } + /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() + * related: OS2969 (same as above) */ + + LOGPFSML(fi, LOGL_NOTICE, "No support for MSC-originated SCCP Connections yet\n"); + osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, 0); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We've sent the CONNECTION.req to the SCCP provider and are waiting for CC from MSC */ +static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case GSCON_EV_A_CONN_CFM: + /* MSC has confirmed the connection, we now change into the + * active state and wait there for further operations */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + /* if there's user payload, forward it just like EV_MT_DTAP */ + /* FIXME: Question: if there's user payload attached to the CC, forward it like EV_MT_DTAP? */ + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We're on an active subscriber connection, passing DTAP back and forth */ +static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + struct mgcp_conn_peer conn_peer; + int rc; + + switch (event) { + case GSCON_EV_A_ASSIGNMENT_CMD: + /* MSC requests us to perform assignment, this code section is + * triggered via signal GSCON_EV_A_ASSIGNMENT_CMD from + * bssmap_handle_assignm_req() in osmo_bsc_bssap.c, which does + * the parsing of incoming assignment requests. */ + + LOGPFSML(fi, LOGL_NOTICE, "Channel assignment: chan_mode=%s, full_rate=%i\n", + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.full_rate); + + /* FIXME: We need to check if current channel is sufficient. If + * yes, do MODIFY. If not, do assignment (see commented lines below) */ + + /* FIXME: At the moment, the FSM is constructed in an + * unfortunate way. In case of a voice channel assignment + * we first go through a couple of MGCP related states, + * then reach the state where the actual channel assignment + * happens and then again we perform some MGCP related + * actions and eventually end up in ST_ACTIVE again. This + * could be restructured */ + + switch (conn->user_plane.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + /* A voice channel is requested, so we run down the + * mgcp-ass-mgcp state-chain (see FIXME above) */ + memset(&conn_peer, 0, sizeof(conn_peer)); + conn_peer.call_id = conn->sccp.conn_id; + osmo_strlcpy(conn_peer.endpoint, ENDPOINT_ID, sizeof(conn_peer.endpoint)); + + /* (Pre)Change state and create the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + conn->user_plane.fi_bts = + mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_BTS, + GSCON_EV_MGW_CRCX_RESP_BTS, &conn_peer); + if (!conn->user_plane.fi_bts) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + break; + case GSM48_CMODE_SIGN: + /* A signalling channel is requested, so we perform the + * channel assignment directly without performing any + * MGCP actions. ST_WAIT_ASS_CMPL will see by the + * conn->user_plane.chan_mode parameter that this + * assignment is for a signalling channel and will then + * change back to ST_ACTIVE (here) immediately. */ + rc = gsm0808_assign_req(conn, conn->user_plane.full_rate, conn->user_plane.chan_mode); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + return; + } + + osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); + break; + default: + /* An unsupported channel is requested, so we have to + * reject this request by sending an assignment failure + * message immediately */ + LOGPFSML(fi, LOGL_ERROR, "Requested channel mode is not supported!\n", + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.full_rate); + + /* The requested channel mode is not supported */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, NULL); + sigtran_send(conn, resp, fi); + break; + } + break; + case GSCON_EV_HO_START: + rc = bsc_handover_start_gscon(conn); + if (rc) { + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + return; + } + + /* Note: No timeout is set here, T3103 in handover_logic.c + * will generate a GSCON_EV_HO_TIMEOUT event should the + * handover time out, so we do not need another timeout + * here (maybe its worth to think about giving GSCON + * more power over the actual handover process). */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_HO_COMPL, 0, 0); + break; + case GSCON_EV_A_HO_REQ: + /* FIXME: reject any handover requests with HO FAIL until implemented */ + break; + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Before we may start the channel assignment we need to get an IP/Port for the + * RTP connection from the MGW */ +static void gscon_fsm_wait_crcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer *conn_peer = NULL; + struct msgb *resp = NULL; + int rc; + + switch (event) { + case GSCON_EV_MGW_CRCX_RESP_BTS: + conn_peer = data; + + /* Check if the MGW has assigned an enpoint to us, we can not + * proceed */ + if (strlen(conn_peer->endpoint) <= 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + /* Memorize the endpoint name we got assigned from the MGW. + * When the BTS sided connection is done, we need to create + * a second connection on that same endpoint, so we need + * to know its ID */ + if (!conn->user_plane.mgw_endpoint) + conn->user_plane.mgw_endpoint = talloc_zero_size(conn, MGCP_ENDPOINT_MAXLEN); + OSMO_ASSERT(conn->user_plane.mgw_endpoint); + osmo_strlcpy(conn->user_plane.mgw_endpoint, conn_peer->endpoint, MGCP_ENDPOINT_MAXLEN); + + /* Store the IP-Address and the port the MGW assigned to us, + * then start the channel assignment. */ + conn->user_plane.rtp_port = conn_peer->port; + conn->user_plane.rtp_ip = osmo_ntohl(inet_addr(conn_peer->addr)); + rc = gsm0808_assign_req(conn, conn->user_plane.full_rate, conn->user_plane.chan_mode); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); + break; + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We're waiting for an ASSIGNMENT COMPLETE from MS */ +static void gscon_fsm_wait_ass_cmpl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_lchan *lchan = conn->lchan; + struct mgcp_conn_peer conn_peer; + struct in_addr addr; + struct msgb *resp = NULL; + int rc; + + switch (event) { + case GSCON_EV_RR_ASS_COMPL: + switch (conn->user_plane.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + /* FIXME: What if we are using SCCP-Lite? */ + + /* We are dealing with a voice channel, so we can not + * confirm the assignment directly. We must first do + * some final steps on the MGCP side. */ + + /* Prepare parameters with the information we got during the assignment */ + memset(&conn_peer, 0, sizeof(conn_peer)); + addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); + osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); + conn_peer.port = lchan->abis_ip.bound_port; + + /* (Pre)Change state and modify the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + break; + case GSM48_CMODE_SIGN: + /* Confirm the successful assignment on BSSMAP and + * change back into active state */ + send_ass_compl(lchan, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + /* Unsupported modes should have been already filtered + * by gscon_fsm_active(). If we reach the default + * section here anyway than some unsupported mode must + * have made it into the FSM, this would be a bug, so + * we fire an assertion here */ + OSMO_ASSERT(false); + break; + } + + break; + case GSCON_EV_RR_ASS_FAIL: + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We are waiting for the MGW response to the MDCX */ +static void gscon_fsm_wait_mdcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer conn_peer; + struct sockaddr_in *sin = NULL; + struct msgb *resp = NULL; + + switch (event) { + case GSCON_EV_MGW_MDCX_RESP_BTS: + + /* Prepare parameters with the connection information we got + * with the assignment command */ + memset(&conn_peer, 0, sizeof(conn_peer)); + conn_peer.call_id = conn->sccp.conn_id; + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; + conn_peer.port = osmo_ntohs(sin->sin_port); + osmo_strlcpy(conn_peer.addr, inet_ntoa(sin->sin_addr), sizeof(conn_peer.addr)); + + /* Make sure we use the same endpoint where we created the + * BTS connection. */ + osmo_strlcpy(conn_peer.endpoint, conn->user_plane.mgw_endpoint, sizeof(conn_peer.endpoint)); + + /* (Pre)Change state and create the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_MSC, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + conn->user_plane.fi_msc = + mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_MSC, GSCON_EV_MGW_CRCX_RESP_MSC, + &conn_peer); + if (!conn->user_plane.fi_bts) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + break; + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static void gscon_fsm_wait_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer *conn_peer = NULL; + struct gsm_lchan *lchan = conn->lchan; + struct sockaddr_in *sin = NULL; + + switch (event) { + case GSCON_EV_MGW_CRCX_RESP_MSC: + conn_peer = data; + + /* Store address information we got in response from the CRCX command. */ + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = inet_addr(conn_peer->addr); + sin->sin_port = osmo_ntohs(conn_peer->port); + + /* Send assignment complete message to the MSC */ + send_ass_compl(lchan, fi); + + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + + break; + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We're waiting for a MODE MODIFY ACK from MS + BTS */ +static void gscon_fsm_wait_mode_modify_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_lchan *lchan = conn->lchan; + + switch (event) { + case GSCON_EV_RR_MODE_MODIFY_ACK: + /* we assume that not only have we received the RR MODE_MODIFY_ACK, but + * actually that also the BTS side of the channel mode has been changed accordingly */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + + /* FIXME: Check if this requires special handling. For now I assume that the send_ass_compl() + * can be used. But I am not sure. */ + send_ass_compl(lchan, fi); + + break; + /* FIXME: Do we need to handle DTAP traffic in this state? Maybe yes? Needs to be checked. */ + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static void gscon_fsm_clearing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp; + + switch (event) { + case GSCON_EV_RSL_CLEAR_COMPL: + resp = gsm0808_create_clear_complete(); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Wait for the handover logic to tell us whether the handover completed, + * failed or has timed out */ +static void gscon_fsm_wait_ho_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer conn_peer; + struct gsm_lchan *lchan = conn->lchan; + struct in_addr addr; + struct msgb *resp; + int rc; + + switch (event) { + case GSCON_EV_HO_COMPL: + /* The handover logic informs us that the handover has been + * completet. Now we have to tell the MGW the IP/Port on the + * new BTS so that the uplink RTP traffic can be redirected + * there. */ + + /* Prepare parameters with the information we got during the + * handover procedure (via IPACC) */ + memset(&conn_peer, 0, sizeof(conn_peer)); + addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); + osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); + conn_peer.port = lchan->abis_ip.bound_port; + + /* (Pre)Change state and modify the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_HO_TIMEOUT_TIMER_NR); + rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); + if (rc != 0) { + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + return; + } + break; + case GSCON_EV_HO_TIMEOUT: + case GSCON_EV_HO_FAIL: + /* The handover logic informs us that the handover failed for + * some reason. This means the phone stays on the TS/BTS on + * which it currently is. We will change back to the active + * state again as there are no further operations needed */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Wait for the MGW to confirm handover related modification of the connection + * parameters */ +static void gscon_fsm_wait_mdcx_bts_ho(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + switch (event) { + case GSCON_EV_MGW_MDCX_RESP_BTS: + /* The MGW has confirmed the handover MDCX, and the handover + * is now also done on the RTP side. We may now change back + * to the active state. */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case GSCON_EV_MO_DTAP: + forward_dtap((struct msgb *)data, conn, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +#define EV_TRANSPARENT_SCCP S(GSCON_EV_TX_SCCP) | S(GSCON_EV_MO_DTAP) | S(GSCON_EV_MT_DTAP) + +static const struct osmo_fsm_state gscon_fsm_states[] = { + [ST_INIT] = { + .name = OSMO_STRINGIFY(INIT), + .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND), + .out_state_mask = S(ST_WAIT_CC), + .action = gscon_fsm_init, + }, + [ST_WAIT_CC] = { + .name = OSMO_STRINGIFY(WAIT_CC), + .in_event_mask = S(GSCON_EV_A_CONN_CFM), + .out_state_mask = S(ST_ACTIVE), + .action = gscon_fsm_wait_cc, + }, + [ST_ACTIVE] = { + .name = OSMO_STRINGIFY(ACTIVE), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_A_ASSIGNMENT_CMD) | S(GSCON_EV_A_HO_REQ) | + S(GSCON_EV_HO_START), + .out_state_mask = S(ST_CLEARING) | S(ST_WAIT_CRCX_BTS) | S(ST_WAIT_ASS_CMPL) | + S(ST_WAIT_MODE_MODIFY_ACK) | S(ST_WAIT_MO_HO_CMD) | S(ST_WAIT_HO_COMPL), + .action = gscon_fsm_active, + }, + [ST_WAIT_CRCX_BTS] = { + .name = OSMO_STRINGIFY(WAIT_CRCX_BTS), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_BTS), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_ASS_CMPL), + .action = gscon_fsm_wait_crcx_bts, + }, + [ST_WAIT_ASS_CMPL] = { + .name = OSMO_STRINGIFY(WAIT_ASS_CMPL), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_ASS_COMPL) | S(GSCON_EV_RR_ASS_FAIL), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS), + .action = gscon_fsm_wait_ass_cmpl, + }, + [ST_WAIT_MDCX_BTS] = { + .name = OSMO_STRINGIFY(WAIT_MDCX_BTS), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CRCX_MSC), + .action = gscon_fsm_wait_mdcx_bts, + }, + [ST_WAIT_CRCX_MSC] = { + .name = OSMO_STRINGIFY(WAIT_CRCX_MSC), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_MSC), + .out_state_mask = S(ST_ACTIVE), + .action = gscon_fsm_wait_crcx_msc, + }, + [ST_WAIT_MODE_MODIFY_ACK] = { + .name = OSMO_STRINGIFY(WAIT_MODE_MODIFY_ACK), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_MODE_MODIFY_ACK), + .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .action = gscon_fsm_wait_mode_modify_ack, + }, + [ST_CLEARING] = { + .name = OSMO_STRINGIFY(CLEARING), + .in_event_mask = S(GSCON_EV_RSL_CLEAR_COMPL), + .action = gscon_fsm_clearing, + }, + + /* TODO: external handover, probably it makes sense to break up the + * program flow in handover_logic.c a bit and handle some of the logic + * here? */ + [ST_WAIT_MT_HO_ACC] = { + .name = OSMO_STRINGIFY(WAIT_MT_HO_ACC), + }, + [ST_WAIT_MT_HO_COMPL] = { + .name = OSMO_STRINGIFY(WAIT_MT_HO_COMPL), + }, + [ST_WAIT_MO_HO_CMD] = { + .name = OSMO_STRINGIFY(WAIT_MO_HO_CMD), + }, + [ST_MO_HO_PROCEEDING] = { + .name = OSMO_STRINGIFY(MO_HO_PROCEEDING), + }, + + /* Internal handover */ + [ST_WAIT_HO_COMPL] = { + .name = OSMO_STRINGIFY(WAIT_HO_COMPL), + .in_event_mask = S(GSCON_EV_HO_COMPL) | S(GSCON_EV_HO_FAIL) | S(GSCON_EV_HO_TIMEOUT), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS_HO), + .action = gscon_fsm_wait_ho_compl, + }, + [ST_WAIT_MDCX_BTS_HO] = { + .name = OSMO_STRINGIFY(WAIT_MDCX_BTS_HO), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), + .action = gscon_fsm_wait_mdcx_bts_ho, + .out_state_mask = S(ST_ACTIVE), + }, +}; + +static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + + /* When a connection on the MGW fails, make sure that the reference + * in our book-keeping is erased. */ + switch (event) { + case GSCON_EV_MGW_FAIL_BTS: + conn->user_plane.fi_bts = NULL; + break; + case GSCON_EV_MGW_FAIL_MSC: + conn->user_plane.fi_msc = NULL; + break; + } + + /* Regular allstate event processing */ + switch (event) { + case GSCON_EV_MGW_FAIL_BTS: + case GSCON_EV_MGW_FAIL_MSC: + /* Note: An MGW connection die per definition at any time. + * However, if it dies during the assignment we must return + * with an assignment failure */ + OSMO_ASSERT(fi->state != ST_INIT && fi->state != ST_WAIT_CC) + if (fi->state == ST_WAIT_CRCX_BTS || fi->state == ST_WAIT_ASS_CMPL || fi->state == ST_WAIT_MDCX_BTS + || fi->state == ST_WAIT_CRCX_MSC) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + } + break; + case GSCON_EV_A_CLEAR_CMD: + /* MSC tells us to cleanly shut down */ + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + gsm0808_clear(conn); + /* FIXME: Release all terestrial resources in ST_CLEARING */ + /* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel + * release to be completed or for the guard timer to expire before returning the + * CLEAR COMPLETE message" */ + + /* Close MGCP connections */ + toss_mgcp_conn(conn, fi); + + /* FIXME: Question: Is this a hack to force a clear complete from internel? + * nobody seems to send the event from outside? */ + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RSL_CLEAR_COMPL, NULL); + break; + case GSCON_EV_A_DISC_IND: + /* MSC or SIGTRAN network has hard-released SCCP connection, + * terminate the FSM now. */ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); + break; + case GSCON_EV_RLL_REL_IND: + /* BTS reports that one of the LAPDm data links was released */ + /* send proper clear request to MSC */ + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE); + sigtran_send(conn, resp, fi); + break; + case GSCON_EV_RSL_CONN_FAIL: + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + sigtran_send(conn, resp, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send); + +static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + if (conn->ho) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing handover state\n"); + bsc_clear_handover(conn, 1); + conn->ho = NULL; + } + + if (conn->secondary_lchan) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing secondary_lchan\n"); + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + conn->secondary_lchan = NULL; + } + if (conn->lchan) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing lchan\n"); + lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); + conn->lchan = NULL; + } + + if (conn->bsub) { + LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n"); + bsc_subscr_put(conn->bsub); + conn->bsub = NULL; + } + + if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) { + LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n"); + struct bsc_msc_data *msc = conn->sccp.msc; + /* FIXME: include a proper cause value / error message? */ + osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0); + conn->sccp.state = SUBSCR_SCCP_ST_NONE; + } + + /* drop pending messages */ + ho_dtap_cache_flush(conn, 0); + + penalty_timers_free(&conn->hodec2.penalty_timers); + + llist_del(&conn->entry); + talloc_free(conn); + fi->priv = NULL; +} + +static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* Make sure all possibly still open MGCP connections get closed */ + toss_mgcp_conn(conn, fi); +} + +static int gscon_timer_cb(struct osmo_fsm_inst *fi) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + + switch (fi->T) { + case 993210: + /* MSC has not responded/confirmed connection witH CC */ + /* N-DISCONNET.req is sent in gscon_cleanup() above */ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + case GSM0808_T10_TIMER_NR: /* Assignment Failed */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case MGCP_MGW_TIMEOUT_TIMER_NR: /* Assignment failed (no response from MGW) */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case MGCP_MGW_HO_TIMEOUT_TIMER_NR: /* Handover failed (no response from MGW) */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + OSMO_ASSERT(false); + } + return 0; +} + +static struct osmo_fsm gscon_fsm = { + .name = "SUBSCR_CONN", + .states = gscon_fsm_states, + .num_states = ARRAY_SIZE(gscon_fsm_states), + .allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_RSL_CONN_FAIL) | + S(GSCON_EV_RLL_REL_IND) | S(GSCON_EV_MGW_FAIL_BTS) | S(GSCON_EV_MGW_FAIL_MSC), + .allstate_action = gscon_fsm_allstate, + .cleanup = gscon_cleanup, + .pre_term = gscon_pre_term, + .timer_cb = gscon_timer_cb, + .log_subsys = DMSC, + .event_names = gscon_fsm_event_names, +}; + +/* Allocate a subscriber connection and its associated FSM */ +struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) +{ + struct gsm_subscriber_connection *conn; + static bool g_initialized = false; + + if (!g_initialized) { + osmo_fsm_register(&gscon_fsm); + g_initialized = true; + } + + conn = talloc_zero(net, struct gsm_subscriber_connection); + if (!conn) + return NULL; + + conn->network = net; + INIT_LLIST_HEAD(&conn->ho_dtap_cache); + /* BTW, penalty timers will be initialized on-demand. */ + conn->sccp.conn_id = -1; + + /* don't allocate from 'conn' context, as gscon_cleanup() will call talloc_free(conn) before + * libosmocore will call talloc_free(conn->fi), i.e. avoid use-after-free during cleanup */ + conn->fi = osmo_fsm_inst_alloc(&gscon_fsm, net, conn, LOGL_NOTICE, NULL); + if (!conn->fi) { + talloc_free(conn); + return NULL; + } + + llist_add_tail(&conn->entry, &net->subscr_conns); + return conn; +} diff --git a/src/libbsc/handover_decision_2.c b/src/libbsc/handover_decision_2.c index 467a19d53..7ac54df95 100644 --- a/src/libbsc/handover_decision_2.c +++ b/src/libbsc/handover_decision_2.c @@ -1150,7 +1150,7 @@ static void on_measurement_report(struct gsm_meas_rep *mr) LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n"); return; } - if (lchan->conn->ho_lchan) { + if (lchan->conn->ho) { LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Handover already triggered\n"); return; } @@ -1349,7 +1349,7 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int break; /* omit if there is an ongoing ho/as */ if (!lc->conn || lc->conn->secondary_lchan - || lc->conn->ho_lchan) + || lc->conn->ho) break; /* We desperately want to resolve congestion, ignore rxlev when * collecting candidates by passing include_weaker_rxlev=true. */ @@ -1365,7 +1365,7 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int /* omit of there is an ongoing ho/as */ if (!lc->conn || lc->conn->secondary_lchan - || lc->conn->ho_lchan) + || lc->conn->ho) continue; /* We desperately want to resolve congestion, ignore rxlev when * collecting candidates by passing include_weaker_rxlev=true. */ diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c index cdc21f56f..960bf6993 100644 --- a/src/libbsc/handover_logic.c +++ b/src/libbsc/handover_logic.c @@ -40,6 +40,7 @@ #include #include #include +#include static LLIST_HEAD(bsc_handovers); static LLIST_HEAD(handover_decision_callbacks); @@ -81,70 +82,105 @@ static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan) int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts, enum gsm_chan_t new_lchan_type) { - struct gsm_network *network; - struct gsm_lchan *new_lchan; + struct gsm_subscriber_connection *conn; struct bsc_handover *ho; static uint8_t ho_ref = 0; - int rc; - bool do_assignment = false; + bool do_assignment; + + OSMO_ASSERT(old_lchan); /* don't attempt multiple handovers for the same lchan at * the same time */ if (bsc_ho_by_old_lchan(old_lchan)) return -EBUSY; + conn = old_lchan->conn; + if (!conn) { + LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); + return -ENOSPC; + } + if (!new_bts) new_bts = old_lchan->ts->trx->bts; - do_assignment = (new_bts == old_lchan->ts->trx->bts); - - network = new_bts->network; + OSMO_ASSERT(new_bts); - rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]); + do_assignment = (new_bts == old_lchan->ts->trx->bts); - if (!old_lchan->conn) { - LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); - return -ENOSPC; + ho = talloc_zero(conn, struct bsc_handover); + if (!ho) { + LOGP(DHO, LOGL_FATAL, "Out of Memory\n"); + return -ENOMEM; } + ho->from_hodec_id = from_hodec_id; + ho->old_lchan = old_lchan; + ho->new_bts = new_bts; + ho->new_lchan_type = new_lchan_type; + ho->ho_ref = ho_ref++; + ho->inter_cell = !do_assignment; + ho->async = true; + llist_add(&ho->list, &bsc_handovers); + + conn->ho = ho; - DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Beginning with handover operation...\n", + DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Initiating %s...\n", old_lchan->ts->trx->bts->nr, old_lchan->ts->trx->nr, old_lchan->ts->nr, old_lchan->nr, gsm_pchan_name(old_lchan->ts->pchan), new_bts->nr, - gsm_lchant_name(new_lchan_type)); + gsm_lchant_name(new_lchan_type), + do_assignment ? "Assignment" : "Handover"); - new_lchan = lchan_alloc(new_bts, new_lchan_type, 0); - if (!new_lchan) { - LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(new_lchan_type)); - rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]); - return -ENOSPC; - } + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HO_START, NULL); +} + +/*! Start actual handover. Call bsc_handover_start() instead; The only legal caller is the GSCON FSM in + * bsc_subscr_conn_fsm.c. */ +int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn) +{ + int rc; + struct gsm_network *network = conn->network; + struct bsc_handover *ho = conn->ho; + struct gsm_lchan *old_lchan; + struct gsm_lchan *new_lchan; - ho = talloc_zero(tall_bsc_ctx, struct bsc_handover); if (!ho) { - LOGP(DHO, LOGL_FATAL, "Out of Memory\n"); - lchan_free(new_lchan); - return -ENOMEM; + LOGP(DHO, LOGL_ERROR, "%s: Requested to start handover, but conn->ho is NULL\n", + bsc_subscr_name(conn->bsub)); + return -EINVAL; + } + + OSMO_ASSERT(ho->old_lchan && ho->new_bts); + + if (ho->old_lchan->conn != conn) { + LOGP(DHO, LOGL_ERROR, + "%s: Requested to start handover, but the lchan does not belong to this conn\n", + bsc_subscr_name(conn->bsub)); + return -EINVAL; + } + + rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]); + + ho->new_lchan = lchan_alloc(ho->new_bts, ho->new_lchan_type, 0); + if (!ho->new_lchan) { + LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(ho->new_lchan_type)); + rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]); + return -ENOSPC; } - ho->from_hodec_id = from_hodec_id; - ho->old_lchan = old_lchan; - ho->new_lchan = new_lchan; - ho->ho_ref = ho_ref++; - ho->inter_cell = !do_assignment; - ho->async = true; - LOGPHO(ho, LOGL_INFO, "Triggering %s\n", do_assignment? "Assignment" : "Handover"); + LOGPHO(ho, LOGL_INFO, "Triggering %s\n", ho->inter_cell? "Handover" : "Assignment"); /* copy some parameters from old lchan */ + old_lchan = ho->old_lchan; + new_lchan = ho->new_lchan; memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr)); - if (do_assignment) { + if (!ho->inter_cell) { new_lchan->ms_power = old_lchan->ms_power; new_lchan->rqd_ta = old_lchan->rqd_ta; } else { new_lchan->ms_power = - ms_pwr_ctl_lvl(new_bts->band, new_bts->ms_max_power); + ms_pwr_ctl_lvl(ho->new_bts->band, ho->new_bts->ms_max_power); /* FIXME: do we have a better idea of the timing advance? */ //new_lchan->rqd_ta = old_lchan->rqd_ta; } @@ -154,24 +190,21 @@ int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, sizeof(new_lchan->mr_ms_lv)); memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, sizeof(new_lchan->mr_bts_lv)); - new_lchan->conn = old_lchan->conn; - new_lchan->conn->ho_lchan = new_lchan; + new_lchan->conn = conn; rc = rsl_chan_activate_lchan(new_lchan, ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC, ho->ho_ref); if (rc < 0) { LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n", - do_assignment? "Assignment" : "Handover", rc); - new_lchan->conn->ho_lchan = NULL; - new_lchan->conn = NULL; - talloc_free(ho); + ho->inter_cell? "Handover" : "Assignment", rc); lchan_free(new_lchan); + ho->new_lchan = NULL; + bsc_clear_handover(conn, 0); return rc; } rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); - llist_add(&ho->list, &bsc_handovers); /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */ return 0; @@ -180,26 +213,20 @@ int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, /* clear any operation for this connection */ void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan) { - struct bsc_handover *ho; - - ho = bsc_ho_by_new_lchan(conn->ho_lchan); - - - if (!ho && conn->ho_lchan) - LOGP(DHO, LOGL_ERROR, "BUG: We lost some state.\n"); + struct bsc_handover *ho = conn->ho; - if (!ho) { - LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + if (!ho) return; - } - - conn->ho_lchan->conn = NULL; - conn->ho_lchan = NULL; - if (free_lchan) - lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END); + if (ho->new_lchan) { + ho->new_lchan->conn = NULL; + if (free_lchan) + lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END); + ho->new_lchan = NULL; + } handover_free(ho); + conn->ho = NULL; } /* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */ @@ -211,10 +238,10 @@ static void ho_T3103_cb(void *_ho) DEBUGP(DHO, "HO T3103 expired\n"); rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]); - ho->new_lchan->conn->ho_lchan = NULL; - ho->new_lchan->conn = NULL; - lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END); - handover_free(ho); + /* Inform the GSCON FSM about the timed out handover */ + osmo_fsm_inst_dispatch(ho->old_lchan->conn->fi, GSCON_EV_HO_TIMEOUT, NULL); + + bsc_clear_handover(ho->old_lchan->conn, 1); } /* RSL has acknowledged activation of the new lchan */ @@ -263,12 +290,7 @@ static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) if (hdc && hdc->on_ho_chan_activ_nack) hdc->on_ho_chan_activ_nack(ho); - new_lchan->conn->ho_lchan = NULL; - new_lchan->conn = NULL; - handover_free(ho); - - /* FIXME: maybe we should try to allocate a new LCHAN here? */ - + bsc_clear_handover(new_lchan->conn, 0); return 0; } @@ -296,16 +318,19 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) if (ho->old_lchan != new_lchan->conn->lchan) LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n"); - if (new_lchan != new_lchan->conn->ho_lchan) + if (new_lchan->conn->ho != ho) LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n"); - new_lchan->conn->ho_lchan = NULL; new_lchan->conn->lchan = new_lchan; ho->old_lchan->conn = NULL; lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END); handover_free(ho); + new_lchan->conn->ho = NULL; + + /* Inform the GSCON FSM that the handover is complete */ + osmo_fsm_inst_dispatch(new_lchan->conn->fi, GSCON_EV_HO_COMPL, NULL); return 0; } @@ -314,7 +339,6 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) { struct gsm_network *net = old_lchan->ts->trx->bts->network; struct bsc_handover *ho; - struct gsm_lchan *new_lchan; struct handover_decision_callbacks *hdc; ho = bsc_ho_by_old_lchan(old_lchan); @@ -329,16 +353,10 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]); - new_lchan = ho->new_lchan; - - /* release the channel and forget about it */ - ho->new_lchan->conn->ho_lchan = NULL; - ho->new_lchan->conn = NULL; - handover_free(ho); - - lchan_release(new_lchan, 0, RSL_REL_LOCAL_END); - + bsc_clear_handover(ho->new_lchan->conn, 1); + /* Inform the GSCON FSM that the handover failed */ + osmo_fsm_inst_dispatch(old_lchan->conn->fi, GSCON_EV_HO_FAIL, NULL); return 0; } diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index 3019470d3..cc9674396 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -30,7 +30,6 @@ osmo_bsc_SOURCES = \ osmo_bsc_vty.c \ osmo_bsc_api.c \ osmo_bsc_grace.c \ - osmo_bsc_mgcp.c \ osmo_bsc_msc.c \ osmo_bsc_sigtran.c \ osmo_bsc_filter.c \ diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c index 75dae1293..40c06dd50 100644 --- a/src/osmo-bsc/osmo_bsc_api.c +++ b/src/osmo-bsc/osmo_bsc_api.c @@ -17,6 +17,7 @@ * */ +#include #include #include #include @@ -31,24 +32,23 @@ #include -#define return_when_not_connected(conn) \ - if (conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED) {\ - LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \ - return; \ - } +/* Check if we have a proper connection to the MSC */ +static bool msc_connected(struct gsm_subscriber_connection *conn) +{ + /* No subscriber conn at all */ + if (!conn) + return false; -#define return_when_not_connected_val(conn, ret) \ - if (conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED) {\ - LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \ - return ret; \ - } + /* Connection to MSC not established */ + if (!conn->sccp.msc) + return false; -#define queue_msg_or_return(resp) \ - if (!resp) { \ - LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \ - return; \ - } \ - osmo_bsc_sigtran_send(conn, resp); + /* Reset procedure not (yet) executed */ + if (a_reset_conn_ready(conn->sccp.msc->a.reset) == false) + return false; + + return true; +} static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause); static int complete_layer3(struct gsm_subscriber_connection *conn, @@ -136,24 +136,33 @@ static int bsc_filter_data(struct gsm_subscriber_connection *conn, static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) { + int rc; struct msgb *resp; - return_when_not_connected(conn); - LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci); + if (!msc_connected(conn)) + return; + LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci); resp = gsm0808_create_sapi_reject(dlci); - queue_msg_or_return(resp); + rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); + if (rc != 0) + msgb_free(resp); } static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr) { + int rc; struct msgb *resp; - return_when_not_connected(conn); + + if (!msc_connected(conn)) + return; LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n"); resp = gsm0808_create_cipher_complete(msg, chosen_encr); - queue_msg_or_return(resp); + rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); + if (rc != 0) + msgb_free(resp); } static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn, @@ -274,15 +283,11 @@ static int complete_layer3(struct gsm_subscriber_connection *conn, resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), NULL); if (!resp) { LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n"); - osmo_bsc_sigtran_del_conn(conn); + //osmo_bsc_sigtran_del_conn(conn); return BSC_API_CONN_POL_REJECT; } - if (osmo_bsc_sigtran_open_conn(conn, resp) != 0) { - osmo_bsc_sigtran_del_conn(conn); - msgb_free(resp); - return BSC_API_CONN_POL_REJECT; - } + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_REQ, resp); return BSC_API_CONN_POL_ACCEPT; } @@ -307,7 +312,7 @@ static int move_to_msc(struct gsm_subscriber_connection *_conn, */ if (complete_layer3(_conn, msg, msc) != BSC_API_CONN_POL_ACCEPT) { gsm0808_clear(_conn); - bsc_subscr_con_free(_conn); + //bsc_subscr_con_free(_conn); return 1; } @@ -376,8 +381,9 @@ static int handle_cc_setup(struct gsm_subscriber_connection *conn, static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) { int lu_cause; - struct msgb *resp; - return_when_not_connected(conn); + + if (!msc_connected(conn)) + return; LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id); @@ -399,16 +405,22 @@ static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, st bsc_scan_bts_msg(conn, msg); - resp = gsm0808_create_dtap(msg, link_id); - queue_msg_or_return(resp); + /* Store link_id in msg->cb */ + OBSC_LINKID_CB(msg) = link_id; + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MO_DTAP, msg); } static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause, uint8_t chosen_channel, uint8_t encr_alg_id, uint8_t speech_model) { - struct msgb *resp; - return_when_not_connected(conn); + if (!msc_connected(conn)) + return; + + conn->lchan->abis_ip.ass_compl.rr_cause = rr_cause; + conn->lchan->abis_ip.ass_compl.chosen_channel = chosen_channel; + conn->lchan->abis_ip.ass_compl.encr_alg_id = encr_alg_id; + conn->lchan->abis_ip.ass_compl.speech_mode = speech_model; if (is_ipaccess_bts(conn_get_bts(conn)) && conn->user_plane.rtp_ip) { /* NOTE: In a network that makes use of an IPA base station @@ -419,37 +431,29 @@ static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_ * postpone the AoIP assignment completed message until we * know the RTP IP/Port combination. */ LOGP(DMSC, LOGL_INFO, "POSTPONE MSC ASSIGN COMPL\n"); - conn->lchan->abis_ip.ass_compl.rr_cause = rr_cause; - conn->lchan->abis_ip.ass_compl.chosen_channel = chosen_channel; - conn->lchan->abis_ip.ass_compl.encr_alg_id = encr_alg_id; - conn->lchan->abis_ip.ass_compl.speech_mode = speech_model; conn->lchan->abis_ip.ass_compl.valid = true; } else { /* NOTE: Send the A assignment complete message immediately. */ LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL\n"); - resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel, - encr_alg_id, speech_model); - queue_msg_or_return(resp); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RR_ASS_COMPL, NULL); } } static void bsc_assign_fail(struct gsm_subscriber_connection *conn, uint8_t cause, uint8_t *rr_cause) { - struct msgb *resp; - return_when_not_connected(conn); - LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN FAIL\n"); - - resp = gsm0808_create_assignment_failure(cause, rr_cause); - queue_msg_or_return(resp); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RR_ASS_FAIL, NULL); } static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) { + int rc; struct msgb *resp; - return_when_not_connected_val(conn, 1); + + if (!msc_connected(conn)) + return 1; LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n"); @@ -459,7 +463,10 @@ static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t ca return 1; } - osmo_bsc_sigtran_send(conn, resp); + rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); + if (rc != 0) + msgb_free(resp); + return 1; } @@ -467,12 +474,16 @@ static void bsc_cm_update(struct gsm_subscriber_connection *conn, const uint8_t *cm2, uint8_t cm2_len, const uint8_t *cm3, uint8_t cm3_len) { + int rc; struct msgb *resp; - return_when_not_connected(conn); - resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len); + if (!msc_connected(conn)) + return; - queue_msg_or_return(resp); + resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len); + rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); + if (rc != 0) + msgb_free(resp); } static void bsc_mr_config(struct gsm_subscriber_connection *conn, diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c index 295d8543a..99a3ebdf4 100644 --- a/src/osmo-bsc/osmo_bsc_audio.c +++ b/src/osmo-bsc/osmo_bsc_audio.c @@ -29,7 +29,8 @@ #include #include #include -#include +#include +#include #include @@ -71,13 +72,17 @@ static int handle_abisip_signal(unsigned int subsys, unsigned int signal, break; case S_ABISIP_MDCX_ACK: - if (con->ho_lchan) { - LOGP(DHO, LOGL_DEBUG, "%s -> %s BTS sent MDCX ACK\n", gsm_lchan_name(lchan), - gsm_lchan_name(con->ho_lchan)); + if (con->ho) { + LOGPHO(con->ho, LOGL_DEBUG, "BTS sent MDCX ACK\n"); /* No need to do anything for handover here. As soon as a HANDOVER DETECT * happens, osmo_bsc_mgcp.c will trigger the MGCP MDCX towards MGW by - * receiving an S_LCHAN_HANDOVER_DETECT signal. */ + * receiving an S_LCHAN_HANDOVER_DETECT signal. + * + * FIXME: This will not work, osmo_bsc_mgcp.c is now removed. The + * switchover must be handled by the GSCON FSM because there we + * we instantiate the child FSMs which handle the MGCP traffic. */ #if 0 +/* FIXME: This does not work anymore, we will have to implement this in the GSCON FSM */ /* NOTE: When an ho_lchan exists, the MDCX is part of an * handover operation (intra-bsc). This means we will not * inform the MSC about the event, which means that no @@ -92,7 +97,8 @@ static int handle_abisip_signal(unsigned int subsys, unsigned int signal, * IPA based base stations. See also osmo_bsc_api.c, * function bsc_assign_compl() */ LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL (POSTPONED)\n"); - mgcp_ass_complete(con->user_plane.mgcp_ctx, lchan); + osmo_fsm_inst_dispatch(con->fi, GSCON_EV_RR_ASS_COMPL, NULL); + } break; } diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c index 176a41374..c489300ac 100644 --- a/src/osmo-bsc/osmo_bsc_bssap.c +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -1,6 +1,7 @@ /* GSM 08.08 BSSMAP handling */ /* (C) 2009-2012 by Holger Hans Peter Freyther * (C) 2009-2012 by On-Waves + * (C) 2017 by Harald Welte * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -24,9 +25,9 @@ #include #include #include -#include #include #include +#include #include #include @@ -535,45 +536,6 @@ static int select_best_cipher(uint8_t msc_mask, uint8_t bsc_mask) return -1; } -/* - * GSM 08.08 § 3.1.9.1 and 3.2.1.21... - * release our gsm_subscriber_connection and send message - */ -static int bssmap_handle_clear_command(struct gsm_subscriber_connection *conn, - struct msgb *msg, unsigned int payload_length) -{ - struct msgb *resp; - - /* TODO: handle the cause of this package */ - - LOGP(DMSC, LOGL_INFO, "Releasing all transactions on %p\n", conn); - gsm0808_clear(conn); - - /* generate the clear complete message */ - resp = gsm0808_create_clear_complete(); - if (!resp) { - LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n"); - return -1; - } - - if (conn->user_plane.mgcp_ctx) { - /* NOTE: This is the AoIP case, osmo-bsc has to negotiate with - * the MGCP-GW. For this an mgcp_ctx should be created that - * contains the FSM and some system data. When the connection - * is removed from the MGCP-GW, then osmo_bsc_sigtran_send() - * calls osmo_bsc_sigtran_send(). */ - mgcp_clear_complete(conn->user_plane.mgcp_ctx, resp); - } else { - /* NOTE: This is the SCCP-Lite case, since we do not handle - * the MGCP-GW switching ourselves, we may skip everything - * that is MGCP-GW related and sent the clear complete message - * directly */ - osmo_bsc_sigtran_send(conn, resp); - } - - return 0; -} - /* * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick * the cipher to be used for this. In case we are already using @@ -669,7 +631,7 @@ reject: return -1; } - osmo_bsc_sigtran_send(conn, resp); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); return -1; } @@ -735,118 +697,129 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn, } /* Currently we only support a limited subset of all - * possible channel types. The limitation ends by not using - * multi-slot, limiting the channel coding to speech */ - if (ct.ch_indctr != GSM0808_CHAN_SPEECH) { + * possible channel types, such as multi-slot or CSD */ + switch (ct.ch_indctr) { + case GSM0808_CHAN_DATA: LOGP(DMSC, LOGL_ERROR, "Unsupported channel type, currently only speech is supported!\n"); cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP; goto reject; - } - - /* Detect if a CIC code is present, if so, we use the classic ip.access - * method to calculate the RTP port */ - if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { - conn->user_plane.cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); - timeslot = conn->user_plane.cic & 0x1f; - multiplex = (conn->user_plane.cic & ~0x1f) >> 5; - } else if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) { - /* Decode AoIP transport address element */ - rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr, TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR), - TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR)); - if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; + case GSM0808_CHAN_SPEECH: + /* Detect if a CIC code is present, if so, we use the classic ip.access method to + * calculate the RTP port */ + if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { + conn->user_plane.cic = + osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); + timeslot = conn->user_plane.cic & 0x1f; + multiplex = (conn->user_plane.cic & ~0x1f) >> 5; + } else if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) { + /* Decode AoIP transport address element */ + rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr, + TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR), + TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + aoip = true; + } else { + LOGP(DMSC, LOGL_ERROR, "AoIP transport address and CIC missing. " + "Audio will not work.\n"); + cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; goto reject; } - aoip = true; - } else { - LOGP(DMSC, LOGL_ERROR, "AoIP transport address and CIC missing. Audio will not work.\n"); - cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; - goto reject; - } - /* Decode speech codec list (AoIP) */ - conn->codec_list_present = false; - if (aoip) { - /* Check for speech codec list element */ - if (!TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) { - LOGP(DMSC, LOGL_ERROR, "Mandatory speech codec list not present.\n"); - cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + /* FIXME: At the moment osmo-bsc does not support any other + * A-Interface other than AoIP. So we must reject all + * assignment requests that are not AoIP compliant. However, + * might support other A-Interface dialects lateron again, + * thats why we preserve the logic around the AoIP detection + * here. */ + if (!aoip) { + LOGP(DMSC, LOGL_ERROR, "Requested A-Interface type is not supported! (AoIP only!)\n"); + cause = GSM0808_CAUSE_REQ_A_IF_TYPE_NOT_SUPP; goto reject; } - /* Decode Speech Codec list */ - rc = gsm0808_dec_speech_codec_list(&conn->codec_list, - TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST), - TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST)); - if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n"); - cause = GSM0808_CAUSE_INCORRECT_VALUE; - goto reject; + /* Decode speech codec list (AoIP) */ + conn->codec_list_present = false; + if (aoip) { + + /* Check for speech codec list element */ + if (!TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) { + LOGP(DMSC, LOGL_ERROR, "Mandatory speech codec list not present.\n"); + cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING; + goto reject; + } + + /* Decode Speech Codec list */ + rc = gsm0808_dec_speech_codec_list(&conn->codec_list, + TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST), + TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST)); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n"); + cause = GSM0808_CAUSE_INCORRECT_VALUE; + goto reject; + } + conn->codec_list_present = true; + scl_ptr = &conn->codec_list; } - conn->codec_list_present = true; - scl_ptr = &conn->codec_list; - } - /* Match codec information from the assignment command against the - * local preferences of the BSC */ - rc = match_codec_pref(&full_rate, &chan_mode, &ct, scl_ptr, msc); - if (rc < 0) { - LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type =" - " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", - ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len)); - /* TODO: actually output codec names, e.g. implement gsm0808_permitted_speech_names[] and - * iterate perm_spch. */ - cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; - goto reject; - } - DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type =" - " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", - full_rate? "full rate" : "half rate", - get_value_string(gsm48_chan_mode_names, chan_mode), - ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len)); - - /* Forward the assignment request to lower layers */ - if (aoip) { - /* Store network side RTP connection information, we will - * process this address later after we have established an RTP - * connection to the BTS. This is just for organizational - * reasons, functional wise it would not matter when exactly - * the network side RTP connection is made, as long it is made - * before we return with the assignment complete message. */ - memcpy(&conn->user_plane.aoip_rtp_addr_remote, &rtp_addr, sizeof(rtp_addr)); - - /* Create an assignment request using the MGCP fsm. This FSM - * is directly started when its created (now) and will also - * take care about the further processing (creating RTP - * endpoints, calling gsm0808_assign_req(), responding to - * the assignment request etc... */ - conn->user_plane.mgcp_ctx = mgcp_assignm_req(msc->network, msc->network->mgw.client, - conn, chan_mode, full_rate); - if (!conn->user_plane.mgcp_ctx) { - LOGP(DMSC, LOGL_ERROR, "MGCP / MGW failure, rejecting assignment... (id=%i)\n", - conn->sccp.conn_id); - cause = GSM0808_CAUSE_EQUIPMENT_FAILURE; + /* Match codec information from the assignment command against the + * local preferences of the BSC */ + rc = match_codec_pref(&full_rate, &chan_mode, &ct, scl_ptr, msc); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len)); + /* TODO: actually output codec names, e.g. implement + * gsm0808_permitted_speech_names[] and iterate perm_spch. */ + cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL; goto reject; } - /* We now may return here, the FSM will do all further work */ - return 0; - } else { - /* Note: In the sccp-lite case we to not perform any mgcp operation, - * (the MSC does that for us). We set conn->rtp_ip to 0 and check - * on this later. By this we know that we have to behave accordingly - * to sccp-lite. */ - conn->user_plane.rtp_port = mgcp_timeslot_to_port(multiplex, timeslot, msc->rtp_base); - conn->user_plane.rtp_ip = 0; - return gsm0808_assign_req(conn, chan_mode, full_rate); + DEBUGP(DMSC, "Found matching audio type: %s %s for channel_type =" + " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n", + full_rate? "full rate" : "half rate", + get_value_string(gsm48_chan_mode_names, chan_mode), + ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len)); + + /* Forward the assignment request to lower layers */ + if (aoip) { + /* Store network side RTP connection information, we will + * process this address later after we have established an RTP + * connection to the BTS. This is just for organizational + * reasons, functional wise it would not matter when exactly + * the network side RTP connection is made, as long it is made + * before we return with the assignment complete message. */ + memcpy(&conn->user_plane.aoip_rtp_addr_remote, &rtp_addr, sizeof(rtp_addr)); + } else { + /* Note: In the sccp-lite case we to not perform any mgcp operation, + * (the MSC does that for us). We set conn->rtp_ip to 0 and check + * on this later. By this we know that we have to behave accordingly + * to sccp-lite. */ + conn->user_plane.rtp_port = mgcp_timeslot_to_port(multiplex, timeslot, msc->rtp_base); + conn->user_plane.rtp_ip = 0; + } + conn->user_plane.chan_mode = chan_mode; + conn->user_plane.full_rate = full_rate; + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_ASSIGNMENT_CMD, NULL); + break; + case GSM0808_CHAN_SIGN: + conn->user_plane.chan_mode = GSM48_CMODE_SIGN; + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_ASSIGNMENT_CMD, NULL); + break; + default: + cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS; + goto reject; } + return 0; reject: resp = gsm0808_create_assignment_failure(cause, NULL); OSMO_ASSERT(resp); - osmo_bsc_sigtran_send(conn, resp); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp); return -1; } @@ -897,7 +870,7 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn, switch (msg->l4h[0]) { case BSS_MAP_MSG_CLEAR_CMD: - ret = bssmap_handle_clear_command(conn, msg, length); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, msg); break; case BSS_MAP_MSG_CIPHER_MODE_CMD: ret = bssmap_handle_cipher_mode(conn, msg, length); @@ -958,7 +931,9 @@ static int dtap_rcvmsg(struct gsm_subscriber_connection *conn, /* pass it to the filter for extra actions */ rc = bsc_scan_msc_msg(conn, gsm48); - dtap_rc = gsm0808_submit_dtap(conn, gsm48, header->link_id, 1); + /* Store link_id in msgb->cb */ + OBSC_LINKID_CB(msg) = header->link_id; + dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48); if (rc == BSS_SEND_USSD) bsc_send_welcome_ussd(conn); return dtap_rc; @@ -1016,38 +991,3 @@ int bsc_handle_dt(struct gsm_subscriber_connection *conn, return -1; } - -/* Generate and send assignment complete message */ -int bssmap_send_aoip_ass_compl(struct gsm_lchan *lchan) -{ - struct msgb *resp; - struct gsm0808_speech_codec sc; - struct gsm_subscriber_connection *conn; - - conn = lchan->conn; - - OSMO_ASSERT(lchan->abis_ip.ass_compl.valid); - OSMO_ASSERT(conn); - - LOGP(DMSC, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.conn_id); - - /* Extrapolate speech codec from speech mode */ - gsm0808_speech_codec_from_chan_type(&sc, lchan->abis_ip.ass_compl.speech_mode); - - /* Generate message */ - resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, - lchan->abis_ip.ass_compl.chosen_channel, - lchan->abis_ip.ass_compl.encr_alg_id, - lchan->abis_ip.ass_compl.speech_mode, - &conn->user_plane.aoip_rtp_addr_local, - &sc, - NULL); - - if (!resp) { - LOGP(DMSC, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n", - conn->sccp.conn_id); - return -EINVAL; - } - - return osmo_bsc_sigtran_send(conn, resp); -} diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index af8f83dae..27c5640ea 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -14,7 +14,7 @@ * 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 . + * along with this program. If not, see . * */ @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -511,8 +510,6 @@ int main(int argc, char **argv) exit(1); } - mgcp_init(bsc_gsmnet); - handover_decision_1_init(); hodec2_init(bsc_gsmnet); diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c deleted file mode 100644 index 4b6420e9b..000000000 --- a/src/osmo-bsc/osmo_bsc_mgcp.c +++ /dev/null @@ -1,1149 +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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define S(x) (1 << (x)) - -#define MGCP_MGW_TIMEOUT 4 /* in seconds */ -#define MGCP_MGW_TIMEOUT_TIMER_NR 1 -#define MGCP_BSS_TIMEOUT 4 /* in seconds */ -#define MGCP_BSS_TIMEOUT_TIMER_NR 2 - -#define MGCP_ENDPOINT_FORMAT "%x@mgw" - -/* Some internal cause codes to indicate fault - * condition inside the FSM */ -enum bsc_mgcp_cause_code { - MGCP_ERR_MGW_FAIL, - MGCP_ERR_MGW_INVAL_RESP, - MGCP_ERR_MGW_TX_FAIL, - MGCP_ERR_UNEXP_TEARDOWN, - MGCP_ERR_ASSGMNT_FAIL, - MGCP_ERR_UNSUPP_ADDR_FMT, - MGCP_ERR_BSS_TIMEOUT, - MGCP_ERR_NOMEM -}; - -/* Human readable respresentation of the faul codes, - * will be displayed by handle_error() */ -static const struct value_string bsc_mgcp_cause_codes_names[] = { - {MGCP_ERR_MGW_FAIL, "operation failed on MGW"}, - {MGCP_ERR_MGW_INVAL_RESP, "invalid / unparseable response from MGW"}, - {MGCP_ERR_MGW_TX_FAIL, "failed to transmit MGCP message to MGW"}, - {MGCP_ERR_UNEXP_TEARDOWN, "unexpected connection teardown (BSS)"}, - {MGCP_ERR_ASSGMNT_FAIL, "assignment failure (BSS)"}, - {MGCP_ERR_UNSUPP_ADDR_FMT, "unsupported network address format used (MSC)"}, - {MGCP_ERR_BSS_TIMEOUT, "assignment could not be completed in time (BSS)"}, - {MGCP_ERR_NOMEM, "out of memory"}, - {0, NULL} -}; - -enum fsm_bsc_mgcp_states { - ST_CRCX_BTS, - ST_ASSIGN_PROC, - ST_MDCX_BTS, - ST_CRCX_NET, - ST_ASSIGN_COMPL, - ST_CALL, - ST_MDCX_BTS_HO, - ST_HALT -}; - -enum bsc_mgcp_fsm_evt { - /* Initial event: start off the state machine */ - EV_INIT, - - /* External event: Assignment complete, event is issued shortly before - * the assignment complete message is sent via the A-Interface */ - EV_ASS_COMPLETE, - - /* External event: Teardown event, this event is used to notify the end - * of a call. It is also issued in case of errors to teardown a half - * open connection. */ - EV_TEARDOWN, - - /* External event: Handover event, this event notifies the FSM that a - * handover is required. The FSM will then perform an extra MDCX to - * configure the new connection data at the MGW. The only valid state - * where a Handover event can be received is ST_CALL. */ - EV_HANDOVER, - - /* Internal event: The mgcp_gw has sent its CRCX response for - * the BTS side */ - EV_CRCX_BTS_RESP, - - /* Internal event: The mgcp_gw has sent its MDCX response for - * the BTS side */ - EV_MDCX_BTS_RESP, - - /* Internal event: The mgcp_gw has sent its CRCX response for - * the NET side */ - EV_CRCX_NET_RESP, - - /* Internal event: The mgcp_gw has sent its DLCX response for - * the NET and BTS side */ - EV_DLCX_ALL_RESP, - - /* Internal event: The mgcp_gw has responded to the (Handover-) - MDCX that has been send to update the BTS connection. */ - EV_MDCX_BTS_HO_RESP, -}; - -/* A general error handler function. On error we still have an interest to - * remove a half open connection (if possible). This function will execute - * a controlled jump to the DLCX phase. From there, the FSM will then just - * continue like the call were ended normally */ -#define handle_error(mgcp_ctx, cause) \ - _handle_error(mgcp_ctx, cause, __FILE__, __LINE__) -static void _handle_error(struct mgcp_ctx *mgcp_ctx, enum bsc_mgcp_cause_code cause, - const char *file, int line) -{ - struct osmo_fsm_inst *fi; - - OSMO_ASSERT(mgcp_ctx); - fi = mgcp_ctx->fsm; - OSMO_ASSERT(fi); - - LOGPFSMLSRC(mgcp_ctx->fsm, LOGL_ERROR, file, line, "%s -- graceful shutdown...\n", - get_value_string(bsc_mgcp_cause_codes_names, cause)); - - /* Set the VM into the state where it waits for the call end */ - osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0); - - /* Simulate the call end by sending a teardown event, so that - * the FSM proceeds directly with the DLCX */ - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx); -} - -static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv); - -/* Callback for ST_CRCX_BTS: startup state machine send out CRCX for BTS side */ -static void fsm_crcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = data; - struct gsm_subscriber_connection *conn; - struct msgb *msg; - struct mgcp_msg mgcp_msg; - struct mgcp_client *mgcp; - uint16_t rtp_endpoint; - int rc; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - - rtp_endpoint = mgcp_client_next_endpoint(mgcp); - mgcp_ctx->rtp_endpoint = rtp_endpoint; - - LOGPFSML(fi, LOGL_DEBUG, - "CRCX/BTS: creating connection for the BTS side on MGW endpoint:0x%x...\n", rtp_endpoint); - - /* Generate MGCP message string */ - mgcp_msg = (struct mgcp_msg) { - .verb = MGCP_VERB_CRCX, - .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE), - .call_id = conn->sccp.conn_id, - .conn_mode = MGCP_CONN_LOOPBACK - }; - if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, rtp_endpoint) >= - MGCP_ENDPOINT_MAXLEN) { - handle_error(mgcp_ctx, MGCP_ERR_NOMEM); - return; - } - msg = mgcp_msg_gen(mgcp, &mgcp_msg); - OSMO_ASSERT(msg); - - /* Transmit MGCP message to MGW */ - mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); - rc = mgcp_client_tx(mgcp, msg, crcx_for_bts_resp_cb, mgcp_ctx); - if (rc < 0) { - handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL); - return; - } - - osmo_fsm_inst_state_chg(fi, ST_ASSIGN_PROC, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); -} - -/* Callback for MGCP-Client: handle response for BTS associated CRCX */ -static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv) -{ - struct mgcp_ctx *mgcp_ctx = priv; - int rc; - struct gsm_subscriber_connection *conn; - uint32_t addr; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, - "CRCX/BTS: late MGW response, FSM already terminated -- ignoring...\n"); - return; - } - - if (r->head.response_code != 200) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, - "CRCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment); - handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL); - return; - } - - /* memorize connection identifier */ - osmo_strlcpy(mgcp_ctx->conn_id_bts, r->head.conn_id, sizeof(mgcp_ctx->conn_id_bts)); - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with CI: %s\n", mgcp_ctx->conn_id_bts); - - rc = mgcp_response_parse_params(r); - if (rc) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response\n"); - handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP); - return; - } - - addr = inet_addr(r->audio_ip); - if (addr == INADDR_NONE) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response (invalid IP-address)\n"); - handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP); - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port); - - /* Set the connection details in the conn struct. The code that - * controls the BTS via RSL will take these values and signal them - * to the BTS via RSL/IPACC */ - conn->user_plane.rtp_port = r->audio_port; - conn->user_plane.rtp_ip = osmo_ntohl(addr); - - /* Notify the FSM that we got the response. */ - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_BTS_RESP, mgcp_ctx); -} - -/* Callback for ST_ASSIGN_PROC: An mgcp response has been received, proceed - * with the assignment request */ -static void fsm_proc_assignmnent_req_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = data; - struct gsm_subscriber_connection *conn; - enum gsm48_chan_mode chan_mode; - bool full_rate; - int rc; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - - switch (event) { - case EV_CRCX_BTS_RESP: - break; - default: - handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN); - return; - } - - OSMO_ASSERT(conn); - chan_mode = mgcp_ctx->chan_mode; - full_rate = mgcp_ctx->full_rate; - - LOGPFSML(fi, LOGL_DEBUG, "MGW proceeding assignment request...\n"); - rc = gsm0808_assign_req(conn, chan_mode, full_rate); - - if (rc < 0) { - handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL); - return; - } - - osmo_fsm_inst_state_chg(fi, ST_MDCX_BTS, MGCP_BSS_TIMEOUT, MGCP_BSS_TIMEOUT_TIMER_NR); -} - -static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv); - -/* Callback for ST_MDCX_BTS: When the BSS has completed the assignment, - * proceed with updating the connection for the BTS side */ -static void fsm_mdcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = data; - struct gsm_subscriber_connection *conn; - struct gsm_lchan *lchan; - struct msgb *msg; - struct mgcp_msg mgcp_msg; - struct mgcp_client *mgcp; - uint16_t rtp_endpoint; - struct in_addr addr; - int rc; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - - switch (event) { - case EV_ASS_COMPLETE: - break; - default: - handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN); - return; - } - - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - lchan = mgcp_ctx->lchan; - OSMO_ASSERT(lchan); - - rtp_endpoint = mgcp_ctx->rtp_endpoint; - - addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); - LOGPFSML(fi, LOGL_DEBUG, - "MDCX/BTS: completing connection for the BTS side on MGW endpoint:0x%x, BTS expects RTP input on address %s:%u\n", - rtp_endpoint, inet_ntoa(addr), lchan->abis_ip.bound_port); - - /* Generate MGCP message string */ - mgcp_msg = (struct mgcp_msg) { - .verb = MGCP_VERB_MDCX, - .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID | - MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT), - .call_id = conn->sccp.conn_id, - .conn_id = mgcp_ctx->conn_id_bts, - .conn_mode = MGCP_CONN_RECV_SEND, - .audio_ip = inet_ntoa(addr), - .audio_port = lchan->abis_ip.bound_port - }; - if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >= - sizeof(mgcp_msg.endpoint)) { - handle_error(mgcp_ctx, MGCP_ERR_NOMEM); - return; - } - msg = mgcp_msg_gen(mgcp, &mgcp_msg); - OSMO_ASSERT(msg); - - /* Transmit MGCP message to MGW */ - mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); - rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_resp_cb, mgcp_ctx); - if (rc < 0) { - handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL); - return; - } - - osmo_fsm_inst_state_chg(fi, ST_CRCX_NET, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); -} - -/* Callback for MGCP-Client: handle response for BTS associated MDCX */ -static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv) -{ - struct mgcp_ctx *mgcp_ctx = priv; - int rc; - struct in_addr addr; - struct gsm_lchan *lchan; - - OSMO_ASSERT(mgcp_ctx); - lchan = mgcp_ctx->lchan; - OSMO_ASSERT(lchan); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, - "MDCX/BTS: late MGW response, FSM already terminated -- ignoring...\n"); - return; - } - - if (r->head.response_code != 200) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, - "MDCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment); - handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL); - return; - } - - rc = mgcp_response_parse_params(r); - if (rc) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "MDCX/BTS: Cannot parse MDCX response\n"); - handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP); - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port); - - addr.s_addr = lchan->abis_ip.bound_ip; - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, - "MDCX/BTS: corresponding lchan has been bound to address %s:%u\n", - inet_ntoa(addr), lchan->abis_ip.bound_port); - - /* Notify the FSM that we got the response. */ - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_RESP, mgcp_ctx); -} - -static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv); - -/* Callback for ST_CRCX_NET: An mgcp response has been received, proceed... */ -static void fsm_crcx_net_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = data; - struct gsm_subscriber_connection *conn; - struct msgb *msg; - struct mgcp_msg mgcp_msg; - struct mgcp_client *mgcp; - uint16_t rtp_endpoint; - struct sockaddr_in *sin; - char *addr; - uint16_t port; - int rc; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - - switch (event) { - case EV_MDCX_BTS_RESP: - break; - default: - handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN); - return; - } - - rtp_endpoint = mgcp_ctx->rtp_endpoint; - - LOGPFSML(fi, LOGL_DEBUG, - "CRCX/NET: creating connection for the NET side on MGW endpoint:0x%x...\n", rtp_endpoint); - - /* Currently we only have support for IPv4 in our MGCP software, the - * AoIP part is ready to support IPv6 in theory, because the IE - * parser/generator uses sockaddr_storage for the AoIP transport - * identifier. However, the MGW does not support IPv6 yet. This is - * why we stop here in case some MSC tries to signal IPv6 AoIP - * transport identifiers */ - if (conn->user_plane.aoip_rtp_addr_remote.ss_family != AF_INET) { - LOGPFSML(fi, LOGL_ERROR, - "CRCX/NET: endpoint:%x MSC uses unsupported address format in AoIP transport identifier -- aborting...\n", - rtp_endpoint); - handle_error(mgcp_ctx, MGCP_ERR_UNSUPP_ADDR_FMT); - return; - } - - sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; - addr = inet_ntoa(sin->sin_addr); - port = osmo_ntohs(sin->sin_port); - LOGPFSML(fi, LOGL_DEBUG, "CRCX/NET: MSC expects RTP input on address %s:%u\n", addr, port); - - /* Generate MGCP message string */ - mgcp_msg = (struct mgcp_msg) { - .verb = MGCP_VERB_CRCX, - .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE | - MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT), - .call_id = conn->sccp.conn_id, - .conn_mode = MGCP_CONN_RECV_SEND, - .audio_ip = addr, - .audio_port = port - }; - if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >= - sizeof(mgcp_msg.endpoint)) { - handle_error(mgcp_ctx, MGCP_ERR_NOMEM); - return; - } - msg = mgcp_msg_gen(mgcp, &mgcp_msg); - OSMO_ASSERT(msg); - - /* Transmit MGCP message to MGW */ - mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); - rc = mgcp_client_tx(mgcp, msg, crcx_for_net_resp_cb, mgcp_ctx); - if (rc < 0) { - handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL); - return; - } - - osmo_fsm_inst_state_chg(fi, ST_ASSIGN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); -} - -/* Callback for MGCP-Client: handle response for NET associated CRCX */ -static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv) -{ - struct mgcp_ctx *mgcp_ctx = priv; - int rc; - struct gsm_subscriber_connection *conn; - struct gsm_lchan *lchan; - struct sockaddr_in *sin; - uint32_t addr; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - lchan = mgcp_ctx->lchan; - OSMO_ASSERT(lchan); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, - "CRCX/NET: late MGW response, FSM already terminated -- ignoring...\n"); - return; - } - - if (r->head.response_code != 200) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, - "CRCX/NET: response yields error: %d %s\n", r->head.response_code, r->head.comment); - handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL); - return; - } - - /* memorize connection identifier */ - osmo_strlcpy(mgcp_ctx->conn_id_net, r->head.conn_id, sizeof(mgcp_ctx->conn_id_net)); - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with CI: %s\n", mgcp_ctx->conn_id_net); - - rc = mgcp_response_parse_params(r); - if (rc) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse CRCX response\n"); - handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP); - return; - } - - addr = inet_addr(r->audio_ip); - if (addr == INADDR_NONE) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse response (invalid IP-address)\n"); - handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP); - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with address %s:%u\n", - r->audio_ip, r->audio_port); - - /* Store address */ - sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local; - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = addr; - sin->sin_port = osmo_ntohs(r->audio_port); - - /* Notify the FSM that we got the response. */ - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_NET_RESP, mgcp_ctx); -} - -/* Callback for ST_ASSIGN_COMPL: Send back assignment complete and wait until the call ends */ -static void fsm_send_assignment_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = data; - struct gsm_lchan *lchan; - - OSMO_ASSERT(mgcp_ctx); - - switch (event) { - case EV_CRCX_NET_RESP: - break; - default: - handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN); - return; - } - - lchan = mgcp_ctx->lchan; - OSMO_ASSERT(lchan); - - /* Send assignment completion message via AoIP, this will complete - * the circuit. The message will also contain the port and IP-Address - * where the MGW expects the RTP input from the MSC side */ - bssmap_send_aoip_ass_compl(lchan); - - LOGPFSML(fi, LOGL_DEBUG, "call in progress, waiting for call end...\n"); - - osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0); -} - -static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv); -static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv); - -/* Helper function to perform a connection teardown. This function may be - * called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state - * change to ST_HALT when teardown is done. */ -static void handle_teardown(struct mgcp_ctx *mgcp_ctx) -{ - struct gsm_subscriber_connection *conn; - struct msgb *msg; - struct mgcp_msg mgcp_msg; - struct mgcp_client *mgcp; - uint16_t rtp_endpoint; - int rc; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - - rtp_endpoint = mgcp_ctx->rtp_endpoint; - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, - "DLCX: removing connection for the BTS and NET side on MGW endpoint:0x%x...\n", rtp_endpoint); - - /* We now relase the endpoint back to the pool in order to allow - * other connections to use this endpoint */ - mgcp_client_release_endpoint(rtp_endpoint, mgcp); - - /* Generate MGCP message string */ - mgcp_msg = (struct mgcp_msg) { - .verb = MGCP_VERB_DLCX, - .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID), - .call_id = conn->sccp.conn_id - }; - if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >= - sizeof(mgcp_msg.endpoint)) { - handle_error(mgcp_ctx, MGCP_ERR_NOMEM); - return; - } - msg = mgcp_msg_gen(mgcp, &mgcp_msg); - OSMO_ASSERT(msg); - - /* Transmit MGCP message to MGW */ - mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); - rc = mgcp_client_tx(mgcp, msg, dlcx_for_all_resp_cb, mgcp_ctx); - if (rc < 0) { - handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL); - return; - } - - osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_HALT, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); -} - -/* Helper function to perform a handover (MDCX). This function may be - * called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state - * change to ST_CALL when teardown is done. */ -static void handle_handover(struct mgcp_ctx *mgcp_ctx) -{ - struct gsm_subscriber_connection *conn; - struct msgb *msg; - struct mgcp_msg mgcp_msg; - struct mgcp_client *mgcp; - struct gsm_lchan *ho_lchan; - uint16_t rtp_endpoint; - struct in_addr addr; - int rc; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - ho_lchan = mgcp_ctx->ho_lchan; - OSMO_ASSERT(ho_lchan); - - rtp_endpoint = mgcp_ctx->rtp_endpoint; - - addr.s_addr = osmo_ntohl(ho_lchan->abis_ip.bound_ip); - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, - "MDCX/BTS/HO: handover connection from old BTS to new BTS side on MGW endpoint:0x%x, new BTS expects RTP input on address %s:%u\n\n", - rtp_endpoint, inet_ntoa(addr), ho_lchan->abis_ip.bound_port); - - /* Generate MGCP message string */ - mgcp_msg = (struct mgcp_msg) { - .verb = MGCP_VERB_MDCX, - .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID | - MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | - MGCP_MSG_PRESENCE_AUDIO_PORT), - .call_id = conn->sccp.conn_id, - .conn_id = mgcp_ctx->conn_id_bts, - .conn_mode = MGCP_CONN_RECV_SEND, - .audio_ip = inet_ntoa(addr), - .audio_port = ho_lchan->abis_ip.bound_port}; - if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >= - sizeof(mgcp_msg.endpoint)) { - handle_error(mgcp_ctx, MGCP_ERR_NOMEM); - return; - } - msg = mgcp_msg_gen(mgcp, &mgcp_msg); - OSMO_ASSERT(msg); - - /* Transmit MGCP message to MGW */ - mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg); - rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_ho_resp_cb, mgcp_ctx); - if (rc < 0) { - handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL); - return; - } - - osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); -} - -/* Callback for ST_CALL: Handle call teardown and Handover */ -static void fsm_active_call_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = data; - - OSMO_ASSERT(mgcp_ctx); - - switch (event) { - case EV_TEARDOWN: - handle_teardown(mgcp_ctx); - break; - case EV_HANDOVER: - handle_handover(mgcp_ctx); - break; - } - -} - -/* Callback for MGCP-Client: handle response for BTS/Handover associated MDCX */ -static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv) -{ - struct mgcp_ctx *mgcp_ctx = priv; - - OSMO_ASSERT(mgcp_ctx); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, "MDCX/BTS/HO: late MGW response, FSM already terminated -- ignoring...\n"); - return; - } - - if (r->head.response_code != 200) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, - "MDCX/BTS/HO: response yields error: %d %s\n", r->head.response_code, r->head.comment); - handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL); - return; - } - - /* Notify the FSM that we got the response. */ - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_HO_RESP, mgcp_ctx); -} - -/* Callback for ST_MDCX_BTS_HO: Complete updating the connection data after - * handoverin the call to another BTS */ -static void fsm_complete_handover(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data; - - OSMO_ASSERT(mgcp_ctx); - - switch (event) { - case EV_MDCX_BTS_HO_RESP: - /* The response from the MGW arrived, the connection pointing - * towards the BTS is now updated, so we now change back to - * ST_CALL, where we will wait for the call-end (or another - * handover) */ - LOGPFSML(fi, LOGL_DEBUG, "MDCX/BTS/HO: handover done, waiting for call end...\n"); - osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0); - break; - case EV_HANDOVER: - /* This handles the rare, but possible situation where another - * handover is happening while we still wait for the the MGW to - * complete the current one. In this case we will stop waiting - * for the response and directly move on with that second - * handover */ - handle_handover(mgcp_ctx); - break; - case EV_TEARDOWN: - /* It may happen that the BSS wants to teardown all connections - * while we are still waiting for the MGW to respond. In this - * case we start to teard down the connection immediately */ - handle_teardown(mgcp_ctx); - break; - } -} - -/* Callback for MGCP-Client: handle response for NET associated CRCX */ -static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv) -{ - struct mgcp_ctx *mgcp_ctx = priv; - struct gsm_subscriber_connection *conn; - struct mgcp_client *mgcp; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, - "DLCX: late MGW response, FSM already terminated -- ignoring...\n"); - return; - } - - /* Note: We check the return code, but in case of an error there is - * not much that can be done to recover. However, at least we tryed - * to remove the connection (if there was even any) */ - if (r->head.response_code != 200) { - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, - "DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment); - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "DLCX: MGW has acknowledged the removal of the connections\n"); - - /* Notify the FSM that we got the response. */ - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_DLCX_ALL_RESP, mgcp_ctx); -} - -/* Callback for ST_HALT: Terminate the state machine */ -static void fsm_halt_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data; - struct gsm_subscriber_connection *conn; - - OSMO_ASSERT(mgcp_ctx); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - - /* Send pending sigtran message */ - if (mgcp_ctx->resp) { - LOGPFSML(fi, LOGL_DEBUG, "sending pending sigtran response message...\n"); - osmo_bsc_sigtran_send(conn, mgcp_ctx->resp); - mgcp_ctx->resp = NULL; - } - - /* Destroy the state machine and all context information */ - osmo_fsm_inst_free(mgcp_ctx->fsm); - mgcp_ctx->fsm = NULL; -} - -/* Timer callback to shut down in case of connectivity problems */ -static int fsm_timeout_cb(struct osmo_fsm_inst *fi) -{ - struct mgcp_ctx *mgcp_ctx = fi->priv; - struct mgcp_client *mgcp; - - OSMO_ASSERT(mgcp_ctx); - mgcp = mgcp_ctx->mgcp; - OSMO_ASSERT(mgcp); - - /* Ensure that no sigtran response, is present. Otherwiese we might try - * to send a sigtran response when the sccp connection is already freed. */ - mgcp_ctx->resp = NULL; - - if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) { - /* Note: We were unable to communicate with the MGW, - * unfortunately there is no meaningful action we can take - * now other than giving up. */ - - /* At least release the occupied endpoint ID */ - mgcp_client_release_endpoint(mgcp_ctx->rtp_endpoint, mgcp); - - /* Cancel the transaction that timed out */ - mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans); - - /* Initiate self destruction of the FSM */ - osmo_fsm_inst_state_chg(fi, ST_HALT, 0, 0); - osmo_fsm_inst_dispatch(fi, EV_TEARDOWN, mgcp_ctx); - } else if (fi->T == MGCP_BSS_TIMEOUT_TIMER_NR) - /* Note: If the logic that controls the BSS is unable to - * negotiate a connection, we presumably still have a - * working connection to the MGW, we will try to - * shut down gracefully. */ - handle_error(mgcp_ctx, MGCP_ERR_BSS_TIMEOUT); - else { - /* Note: Ther must not be any unsolicited timers - * in this FSM. If so, we have serious problem. */ - OSMO_ASSERT(false); - } - - return 0; -} - -static struct osmo_fsm_state fsm_bsc_mgcp_states[] = { - - /* Startup state machine, send CRCX to BTS. */ - [ST_CRCX_BTS] = { - .in_event_mask = S(EV_INIT), - .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_ASSIGN_PROC), - .name = OSMO_STRINGIFY(ST_CRCX_BTS), - .action = fsm_crcx_bts_cb, - }, - - /* When the CRCX response for the BTS side is received, then - * proceed the assignment on the BSS side. */ - [ST_ASSIGN_PROC] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_CRCX_BTS_RESP), - .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_BTS), - .name = OSMO_STRINGIFY(ST_ASSIGN_PROC), - .action = fsm_proc_assignmnent_req_cb, - }, - - /* When the BSS has processed the assignment request, - * then send the MDCX command for the BTS side in order to - * update the connections with the actual PORT/IP where the - * BTS expects the RTP input. */ - [ST_MDCX_BTS] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_ASS_COMPLETE), - .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_NET), - .name = OSMO_STRINGIFY(ST_MDCX_BTS), - .action = fsm_mdcx_bts_cb, - }, - - /* When the MDCX response for the BTS siede is received, then - * directly proceed with sending the CRCX command to connect the - * network side. This is done in one phase (no MDCX needed). */ - [ST_CRCX_NET] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_MDCX_BTS_RESP), - .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_ASSIGN_COMPL), - .name = OSMO_STRINGIFY(ST_CRCX_NET), - .action = fsm_crcx_net_cb, - }, - - /* When the CRCX response for the NET side is received. Then - * send the assignment complete message via the A-Interface and - * enter wait state in order to wait for the end of the call. */ - [ST_ASSIGN_COMPL] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_CRCX_NET_RESP), - .out_state_mask = S(ST_HALT) | S(ST_CALL), - .name = OSMO_STRINGIFY(ST_ASSIGN_COMPL), - .action = fsm_send_assignment_complete, - }, - - /* When the call ends, remove all RTP connections from the - * MGW by sending a wildcarded DLCX. In case of a handover, - * go for an extra MDCX to update the connection and land in - * this state again when done. */ - [ST_CALL] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_HANDOVER), - .out_state_mask = S(ST_HALT) | S(ST_MDCX_BTS_HO), - .name = OSMO_STRINGIFY(ST_CALL), - .action = fsm_active_call_cb, - }, - - /* A handover is in progress. When the response to the respective - * MDCX is received, then go back to ST_CALL and wait for the - * call end */ - [ST_MDCX_BTS_HO] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_HANDOVER) | S(EV_MDCX_BTS_HO_RESP), - .out_state_mask = S(ST_HALT) | S(ST_CALL), - .name = OSMO_STRINGIFY(ST_MDCX_BTS_HO), - .action = fsm_complete_handover, - }, - - /* When the MGW confirms that the connections are terminated, - * then halt the state machine. */ - [ST_HALT] = { - .in_event_mask = S(EV_TEARDOWN) | S(EV_DLCX_ALL_RESP), - .out_state_mask = 0, - .name = OSMO_STRINGIFY(ST_HALT), - .action = fsm_halt_cb, - }, -}; - -/* State machine definition */ -static struct osmo_fsm fsm_bsc_mgcp = { - .name = "MGW", - .states = fsm_bsc_mgcp_states, - .num_states = ARRAY_SIZE(fsm_bsc_mgcp_states), - .log_subsys = DMGCP, - .timer_cb = fsm_timeout_cb, -}; - -/* Notify that the a new call begins. This will create a connection for the - * BTS on the MGW and set up the port numbers in struct osmo_bsc_sccp_con. - * After that gsm0808_assign_req() to proceed. - * Parameter: - * ctx: talloc context - * network: associated gsm network - * conn: associated sccp connection - * chan_mode: channel mode (system data, passed through) - * full_rate: full rate flag (system data, passed through) - * Returns an mgcp_context that contains system data and the OSMO-FSM */ -struct mgcp_ctx *mgcp_assignm_req(void *ctx, struct mgcp_client *mgcp, - struct gsm_subscriber_connection *conn, - enum gsm48_chan_mode chan_mode, bool full_rate) -{ - struct mgcp_ctx *mgcp_ctx; - char name[32]; - - OSMO_ASSERT(mgcp); - OSMO_ASSERT(conn); - - OSMO_ASSERT(snprintf(name, sizeof(name), "MGW_%i", conn->sccp.conn_id) < sizeof(name)); - - /* Allocate and configure a new fsm instance */ - mgcp_ctx = talloc_zero(ctx, struct mgcp_ctx); - OSMO_ASSERT(mgcp_ctx); - - mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm_bsc_mgcp, NULL, ctx, LOGL_DEBUG, name); - OSMO_ASSERT(mgcp_ctx->fsm); - mgcp_ctx->fsm->priv = mgcp_ctx; - mgcp_ctx->mgcp = mgcp; - mgcp_ctx->conn = conn; - mgcp_ctx->chan_mode = chan_mode; - mgcp_ctx->full_rate = full_rate; - - /* start state machine */ - OSMO_ASSERT(mgcp_ctx->fsm->state == ST_CRCX_BTS); - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_INIT, mgcp_ctx); - - return mgcp_ctx; -} - -/* Notify that the call has ended, remove all connections from the MGW, - * then send the clear complete message and destroy the FSM instance - * Parameter: - * mgcp_ctx: context information (FSM, and pointer to external system data) - * respmgcp_ctx: pending clear complete message to send via A-Interface */ -void mgcp_clear_complete(struct mgcp_ctx *mgcp_ctx, struct msgb *resp) -{ - struct gsm_subscriber_connection *conn; - - OSMO_ASSERT(mgcp_ctx); - OSMO_ASSERT(resp); - conn = mgcp_ctx->conn; - OSMO_ASSERT(conn); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, - "clear completion attemted on already terminated FSM -- forwarding directly...\n"); - osmo_bsc_sigtran_send(conn, resp); - mgcp_ctx->resp = NULL; - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating call end...\n"); - - mgcp_ctx->resp = resp; - - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx); -} - -/* Notify that the BSS ready, send the assingnment complete message when the - * mgcp connection is completed - * Parameter: - * mgcp_ctx: context information (FSM, and pointer to external system data) - * lchan: needed for sending the assignment complete message via A-Interface */ -void mgcp_ass_complete(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *lchan) -{ - OSMO_ASSERT(mgcp_ctx); - OSMO_ASSERT(lchan); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, "assignment completion attemted on already terminated FSM -- ignored\n"); - mgcp_ctx->lchan = NULL; - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating assignment completion...\n"); - - mgcp_ctx->lchan = lchan; - - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASS_COMPLETE, mgcp_ctx); - - return; -} - -/* Notify that the call got handovered to another BTS, update the connection - * that is pointing to the BTS side with the connection data for the new bts. - * Parameter: - * mgcp_ctx: context information (FSM, and pointer to external system data) - * ho_lchan: the lchan on the new BTS */ -static void mgcp_handover(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *ho_lchan) -{ - OSMO_ASSERT(mgcp_ctx); - OSMO_ASSERT(ho_lchan); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_ERROR, "handover attemted on already terminated FSM -- ignored\n"); - mgcp_ctx->ho_lchan = NULL; - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating handover...\n"); - - mgcp_ctx->ho_lchan = ho_lchan; - - osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_HANDOVER, mgcp_ctx); - - return; -} - -/* GSM 08.58 HANDOVER DETECT has been received */ -static int mgcp_sig_ho_detect(struct gsm_lchan *new_lchan) -{ - struct gsm_subscriber_connection *conn; - if (!new_lchan) { - LOGP(DHO, LOGL_ERROR, "HO Detect signal for NULL lchan\n"); - return -EINVAL; - } - - conn = new_lchan->conn; - - if (!conn) { - LOGP(DHO, LOGL_ERROR, "%s HO Detect for lchan without conn\n", - gsm_lchan_name(new_lchan)); - return -EINVAL; - } - - if (!conn->sccp.conn_id) { - LOGP(DHO, LOGL_ERROR, "%s HO Detect for conn without sccp_conn_id\n", - gsm_lchan_name(new_lchan)); - return -EINVAL; - } - - if (!conn->user_plane.mgcp_ctx) { - LOGP(DHO, LOGL_ERROR, "%s HO Detect for conn without MGCP ctx\n", - gsm_lchan_name(new_lchan)); - return -EINVAL; - } - - mgcp_handover(conn->user_plane.mgcp_ctx, new_lchan); - return 0; -} - -static int mgcp_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct lchan_signal_data *lchan_data; - - switch (subsys) { - case SS_LCHAN: - lchan_data = signal_data; - switch (signal) { - case S_LCHAN_HANDOVER_DETECT: - return mgcp_sig_ho_detect(lchan_data->lchan); - } - break; - default: - break; - } - - return 0; -} - -/* Free an existing mgcp context gracefully - * Parameter: - * mgcp_ctx: context information (FSM, and pointer to external system data) */ -void mgcp_free_ctx(struct mgcp_ctx *mgcp_ctx) -{ - OSMO_ASSERT(mgcp_ctx); - - if (mgcp_ctx->fsm == NULL) { - LOGP(DMGCP, LOGL_DEBUG, "fsm already terminated, freeing only related context information...\n"); - talloc_free(mgcp_ctx); - return; - } - - LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "terminating fsm and freeing related context information...\n"); - - osmo_fsm_inst_free(mgcp_ctx->fsm); - talloc_free(mgcp_ctx); -} - -void mgcp_init(struct gsm_network *net) -{ - osmo_fsm_register(&fsm_bsc_mgcp); - osmo_signal_register_handler(SS_LCHAN, mgcp_sig_cb, NULL); -} diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c index ab903b622..1a31a7c52 100644 --- a/src/osmo-bsc/osmo_bsc_sigtran.c +++ b/src/osmo-bsc/osmo_bsc_sigtran.c @@ -1,8 +1,7 @@ -/* (C) 2017 by sysmocom s.f.m.c. GmbH +/* (C) 2017 by sysmocom s.f.m.c. GmbH, Author: Philipp Maier + * (C) 2017 by Harald Welte * 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 @@ -20,6 +19,7 @@ #include #include +#include #include #include #include @@ -32,7 +32,7 @@ #include #include #include -#include +#include /* A pointer to a list with all involved MSCs * (a copy of the pointer location submitted with osmo_bsc_sigtran_init() */ @@ -127,18 +127,10 @@ static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_add } /* Send data to MSC, use the connection id which MSC it is */ -static int handle_data_from_msc(int conn_id, struct msgb *msg) +static int handle_data_from_msc(struct gsm_subscriber_connection *conn, struct msgb *msg) { - struct gsm_subscriber_connection *conn = get_bsc_conn_by_conn_id(conn_id); - int rc = -EINVAL; - - if (conn) { - msg->l3h = msgb_l2(msg); - rc = bsc_handle_dt(conn, msg, msgb_l2len(msg)); - } else - LOGP(DMSC, LOGL_NOTICE, "incoming data from unknown connection id: %i\n", conn_id); - - return rc; + msg->l3h = msgb_l2(msg); + return bsc_handle_dt(conn, msg, msgb_l2len(msg)); } /* Sent unitdata to MSC, use the point code to determine which MSC it is */ @@ -177,23 +169,32 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) break; case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION): - /* Handle (Reject) inbound connections */ + /* Handle inbound connections */ DEBUGP(DMSC, "N-CONNECT.ind(X->%u)\n", scu_prim->u.connect.conn_id); - LOGP(DMSC, LOGL_DEBUG, "Rejecting inbound SCCP connection...\n"); - rc = osmo_sccp_tx_disconn(scu, scu_prim->u.connect.conn_id, &scu_prim->u.connect.called_addr, 0); + conn = bsc_subscr_con_allocate(bsc_gsmnet); + if (conn) { + conn->sccp.msc = get_msc_by_addr(&scu_prim->u.connect.calling_addr); + /* MSC may be NULL, let the FSM deal with it */ + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_IND, scu_prim); + } else + LOGP(DMSC, LOGL_ERROR, "Unable to alloc subscr_conn for inbound N-CONNECT.ind\n"); break; case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): /* Handle outbound connection confirmation */ + DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id, + osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id); - if (conn) + if (conn) { + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, scu_prim); conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; - if (msgb_l2len(oph->msg) > 0) { - DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id, - osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); - rc = handle_data_from_msc(scu_prim->u.connect.conn_id, oph->msg); - } else - DEBUGP(DMSC, "N-CONNECT.cnf(%u)\n", scu_prim->u.connect.conn_id); + if (msgb_l2len(oph->msg) > 0) + handle_data_from_msc(conn, oph->msg); + } else { + LOGP(DMSC, LOGL_ERROR, "N-CONNET.cfm(%u, %s) for unknown conn?!?\n", + scu_prim->u.connect.conn_id, osmo_hexdump(msgb_l2(oph->msg), + msgb_l2len(oph->msg))); + } break; case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): @@ -202,28 +203,25 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu) osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg))); /* Incoming data is a sign of a vital connection */ - conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id); - if (conn) + conn = get_bsc_conn_by_conn_id(scu_prim->u.data.conn_id); + if (conn) { a_reset_conn_success(conn->sccp.msc->a.reset); - - rc = handle_data_from_msc(scu_prim->u.data.conn_id, oph->msg); + handle_data_from_msc(conn, oph->msg); + } break; case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): + DEBUGP(DMSC, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id, + osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)), + scu_prim->u.disconnect.cause); /* indication of disconnect */ conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id); - if (conn) + if (conn) { conn->sccp.state = SUBSCR_SCCP_ST_NONE; - if (msgb_l2len(oph->msg) > 0) { - DEBUGP(DMSC, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id, - osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)), scu_prim->u.disconnect.cause); - handle_data_from_msc(scu_prim->u.disconnect.conn_id, oph->msg); - } else { - DEBUGP(DMSC, "N-DISCONNECT.ind(%u, cause=%i)\n", scu_prim->u.disconnect.conn_id, - scu_prim->u.disconnect.cause); + if (msgb_l2len(oph->msg) > 0) + handle_data_from_msc(conn, oph->msg); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_DISC_IND, scu_prim); } - if (conn) - rc = osmo_bsc_sigtran_del_conn(conn); break; default: @@ -352,31 +350,6 @@ int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *m return rc; } -/* Delete a connection from the list with open connections - * (called by osmo_bsc_api.c on failing open connections and - * locally, when a connection is closed by the MSC */ -int osmo_bsc_sigtran_del_conn(struct gsm_subscriber_connection *conn) -{ - if (!conn) - return 0; - - LOGP(DMSC, LOGL_ERROR, - "sccp connection (id=%i) not cleared (gsm subscriber connection still active) -- forcefully clearing it now!\n", conn->sccp.conn_id); - - /* This bahaviour might be caused by a bad connection. Maybe we - * will have to go through the reset procedure again */ - a_reset_conn_fail(conn->sccp.msc->a.reset); - - /* Remove mgcp context if existant */ - if (conn->user_plane.mgcp_ctx) - mgcp_free_ctx(conn->user_plane.mgcp_ctx); - - /* free the "conn" and make sure any pending lchans are also free'd */ - bsc_subscr_con_free(conn); - - return 0; -} - /* Send an USSD notification in case we loose the connection to the MSC */ static void bsc_notify_msc_lost(struct gsm_subscriber_connection *conn) { @@ -411,13 +384,9 @@ void osmo_bsc_sigtran_reset(const struct bsc_msc_data *msc) bsc_notify_msc_lost(conn); /* Take down all occopied RF channels */ - gsm0808_clear(conn); - /* Disconnect all Sigtran connections */ - osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0); - /* Delete subscriber connection */ - osmo_bsc_sigtran_del_conn(conn); + osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL); } } } diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 983a3bb04..0c681a560 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -22,7 +22,6 @@ noinst_HEADERS = \ $(NULL) bin_PROGRAMS = \ - bs11_config \ isdnsync \ meas_json \ $(NULL) -- cgit v1.2.3