diff options
Diffstat (limited to 'src/osmo-bsc/bsc_subscr_conn_fsm.c')
-rw-r--r-- | src/osmo-bsc/bsc_subscr_conn_fsm.c | 1199 |
1 files changed, 455 insertions, 744 deletions
diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c index f97b7781c..0ed98d838 100644 --- a/src/osmo-bsc/bsc_subscr_conn_fsm.c +++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c @@ -26,8 +26,8 @@ #include <osmocom/bsc/a_reset.h> #include <osmocom/bsc/bsc_api.h> #include <osmocom/bsc/gsm_data.h> -#include <osmocom/bsc/handover.h> -#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/handover_fsm.h> +#include <osmocom/bsc/lchan_fsm.h> #include <osmocom/bsc/bsc_subscriber.h> #include <osmocom/bsc/osmo_bsc_sigtran.h> #include <osmocom/bsc/osmo_bsc_lcls.h> @@ -37,6 +37,9 @@ #include <osmocom/bsc/bsc_rll.h> #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/gsm_timers.h> +#include <osmocom/bsc/gsm_04_08_rr.h> +#include <osmocom/bsc/mgw_endpoint_fsm.h> +#include <osmocom/bsc/assignment_fsm.h> #include <osmocom/mgcp_client/mgcp_client_fsm.h> #include <osmocom/core/byteswap.h> @@ -48,112 +51,40 @@ #define MGCP_MGW_HO_TIMEOUT 4 /* in seconds */ #define MGCP_MGW_HO_TIMEOUT_TIMER_NR 2 -#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, + ST_ASSIGNMENT, + ST_HANDOVER, /* 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_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_ASSIGNMENT_START, "ASSIGNMENT_START"}, + {GSCON_EV_ASSIGNMENT_END, "ASSIGNMENT_END"}, + {GSCON_EV_HANDOVER_START, "HANDOVER_START"}, + {GSCON_EV_HANDOVER_END, "HANDOVER_END"}, + {GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL"}, + {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_MGW_MDCX_RESP_MSC, "MGW_MDCX_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"}, + {GSCON_EV_MGW_MDCX_RESP_MSC, "MGW_MDCX_RESP_MSC"}, {GSCON_EV_LCLS_FAIL, "LCLS_FAIL"}, - - {0, NULL} + {GSCON_EV_FORGET_LCHAN, "FORGET_LCHAN"}, + {GSCON_EV_FORGET_MGW_ENDPOINT, "FORGET_MGW_ENDPOINT"}, + {} }; -/* Depending on the channel mode and rate, set the codec type that is signalled - * towards the MGW. */ -void bsc_subscr_pick_codec(struct mgcp_conn_peer *conn_peer, struct gsm_subscriber_connection *conn) -{ - switch (conn->user_plane.chan_mode) { - case GSM48_CMODE_SPEECH_V1: - if (conn->user_plane.full_rate) - conn_peer->codecs[0] = CODEC_GSM_8000_1; - else - conn_peer->codecs[0] = CODEC_GSMHR_8000_1; - conn_peer->codecs_len = 1; - break; - case GSM48_CMODE_SPEECH_EFR: - conn_peer->codecs[0] = CODEC_GSMEFR_8000_1; - conn_peer->codecs_len = 1; - break; - case GSM48_CMODE_SPEECH_AMR: - conn_peer->codecs[0] = CODEC_AMR_8000_1; - conn_peer->codecs_len = 1; - break; - default: - conn_peer->codecs_len = 0; - } -} - struct state_timeout conn_fsm_timeouts[32] = { - [ST_WAIT_ASS_CMPL] = { .T = 10 }, - [ST_WAIT_CRCX_BTS] = { .T = 992427 }, - [ST_WAIT_MDCX_BTS] = { .T = 992427 }, - [ST_WAIT_CRCX_MSC] = { .T = 992427 }, - [ST_WAIT_MDCX_BTS_HO] = { .T = 992427 }, [ST_WAIT_CC] = { .T = 993210 }, [ST_CLEARING] = { .T = 999 }, }; @@ -168,158 +99,131 @@ struct state_timeout conn_fsm_timeouts[32] = { -1) /* forward MT DTAP from BSSAP side to RSL side */ -static inline void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, - struct osmo_fsm_inst *fi) +static inline void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg) { - OSMO_ASSERT(fi); OSMO_ASSERT(msg); OSMO_ASSERT(conn); gscon_submit_rsl_dtap(conn, msg, OBSC_LINKID_CB(msg), 1); } -/* 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) +static void gscon_dtap_queue_flush(struct gsm_subscriber_connection *conn, int send); + +int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg) { int rc; + if (!msg) + return -ENOMEM; + /* 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"); + if (conn->fi->state == ST_INIT || conn->fi->state == ST_WAIT_CC) { + LOGPFSML(conn->fi, LOGL_ERROR, "No active SCCP connection, dropping message\n"); msgb_free(msg); - return; + return -ENODEV; } rc = osmo_bsc_sigtran_send(conn, msg); if (rc < 0) - LOGPFSML(fi, LOGL_ERROR, "Unable to deliver SCCP message!\n"); + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver SCCP message\n"); + return rc; } -/* Add the LCLS BSS Status IE to a BSSMAP message. We assume this is - * called on a msgb that was returned by gsm0808_create_ass_compl() */ -static void bssmap_add_lcls_status(struct msgb *msg, enum gsm0808_lcls_status status) +static void gscon_bssmap_clear(struct gsm_subscriber_connection *conn, + enum gsm0808_cause cause) { - OSMO_ASSERT(msg->l3h[0] == BSSAP_MSG_BSS_MANAGEMENT); - OSMO_ASSERT(msg->l3h[2] == BSS_MAP_MSG_ASSIGMENT_COMPLETE || - msg->l3h[2] == BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE || - msg->l3h[2] == BSS_MAP_MSG_HANDOVER_COMPLETE || - msg->l3h[2] == BSS_MAP_MSG_HANDOVER_PERFORMED); - OSMO_ASSERT(msgb_tailroom(msg) >= 2); - - /* append IE to end of message */ - msgb_tv_put(msg, GSM0808_IE_LCLS_BSS_STATUS, status); - /* increment the "length" byte in the BSSAP header */ - msg->l3h[1] += 2; + struct msgb *resp = gsm0808_create_clear_rqst(cause); + gscon_sigtran_send(conn, resp); } -/* Add (append) the LCLS BSS Status IE to a BSSMAP message, if there is any LCLS - * active on the given \a conn */ -static void bssmap_add_lcls_status_if_needed(struct gsm_subscriber_connection *conn, - struct msgb *msg) +/* forward MO DTAP from RSL side to BSSAP side */ +static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) { - enum gsm0808_lcls_status status = lcls_get_status(conn); - if (status != 0xff) { - LOGPFSM(conn->fi, "Adding LCLS BSS-Status (%s) to %s\n", - gsm0808_lcls_status_name(status), - gsm0808_bssmap_name(msg->l3h[2])); - bssmap_add_lcls_status(msg, status); - } + struct msgb *resp = NULL; + + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); + gscon_sigtran_send(conn, resp); } -/* Generate and send assignment complete message */ -static void send_ass_compl(struct gsm_lchan *lchan, struct osmo_fsm_inst *fi, bool voice) +void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_sacch_deact) { - struct msgb *resp; - struct gsm0808_speech_codec sc; - struct gsm0808_speech_codec *sc_ptr = NULL; - struct gsm_subscriber_connection *conn; - struct sockaddr_storage *addr_local = NULL; - int perm_spch = 0; - uint8_t chosen_channel; + if (conn->ho.fi) + handover_end(conn, HO_RESULT_CONN_RELEASE); - conn = lchan->conn; - OSMO_ASSERT(conn); + assignment_reset(conn); - /* apply LCLS configuration (if any) */ - lcls_apply_config(conn); - - LOGPFSML(fi, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.conn_id); - - /* Generate voice related fields */ - if (voice) { - perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode); - switch (conn->sccp.msc->a.asp_proto) { - case OSMO_SS7_ASP_PROT_IPA: - /* don't add any AoIP specific fields. CIC allocated by MSC */ - break; - default: - OSMO_ASSERT(lchan->abis_ip.ass_compl.valid); - addr_local = &conn->user_plane.aoip_rtp_addr_local; - - /* Extrapolate speech codec from speech mode */ - gsm0808_speech_codec_from_chan_type(&sc, perm_spch); - sc_ptr = ≻ - break; - } - /* FIXME: AMR codec configuration must be derived from lchan1! */ - } - - chosen_channel = gsm0808_chosen_channel(lchan->tch_mode, lchan->type); - if (!chosen_channel) - LOGP(DMSC, LOGL_ERROR, "Unknown lchan type or TCH mode: %s\n", gsm_lchan_name(lchan)); + lchan_release(conn->lchan, do_sacch_deact, false, 0); + lchan_forget_conn(conn->lchan); + conn->lchan = NULL; +} - /* Generate message */ - resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, - chosen_channel, - lchan->encr.alg_id, perm_spch, - addr_local, sc_ptr, NULL); +static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *msg = scu_prim->oph.msg; + struct bssmap_header *bs; + uint8_t bssmap_type; - if (!resp) { - LOGPFSML(fi, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n", - conn->sccp.conn_id); + msg->l3h = msgb_l2(msg); + if (!msgb_l3(msg)) { + LOGPFSML(fi, LOGL_ERROR, "internal error: no l3 in msg\n"); + goto refuse; } - /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */ - bssmap_add_lcls_status_if_needed(conn, resp); + if (msgb_l3len(msg) < sizeof(*bs)) { + LOGPFSML(fi, LOGL_NOTICE, "message too short for BSSMAP header (%u < %zu)\n", + msgb_l3len(msg), sizeof(*bs)); + goto refuse; + } - sigtran_send(conn, resp, fi); -} + bs = (struct bssmap_header*)msgb_l3(msg); + if (msgb_l3len(msg) < (bs->length + sizeof(*bs))) { + LOGPFSML(fi, LOGL_NOTICE, + "message too short for length indicated in BSSMAP header (%u < %u)\n", + msgb_l3len(msg), bs->length); + goto refuse; + } -/* forward MO DTAP from RSL side to BSSAP side */ -static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) -{ - struct msgb *resp = NULL; + switch (bs->type) { + case BSSAP_MSG_BSS_MANAGEMENT: + break; + default: + LOGPFSML(fi, LOGL_NOTICE, + "message type not allowed for N-CONNECT: %s\n", gsm0808_bssap_name(bs->type)); + goto refuse; + } - OSMO_ASSERT(msg); - OSMO_ASSERT(conn); + msg->l4h = &msg->l3h[sizeof(*bs)]; + bssmap_type = msg->l4h[0]; - resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); - sigtran_send(conn, resp, fi); -} + LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type), + gsm0808_bssmap_name(bssmap_type)); -/* 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"); + switch (bssmap_type) { + case BSS_MAP_MSG_HANDOVER_RQST: + /* First off, accept the new conn. */ + osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, NULL, 0); - if (conn->user_plane.fi_bts) { - mgcp_conn_delete(conn->user_plane.fi_bts); - conn->user_plane.fi_bts = NULL; - } + /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ + conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; - if (conn->user_plane.fi_msc) { - mgcp_conn_delete(conn->user_plane.fi_msc); - conn->user_plane.fi_msc = NULL; + /* Inter-BSC MT Handover Request, another BSS is handovering to us. */ + handover_start_inter_bsc_in(conn, msg); + return; + default: + break; } - if (conn->user_plane.mgw_endpoint) { - talloc_free(conn->user_plane.mgw_endpoint); - conn->user_plane.mgw_endpoint = NULL; - } + LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n", + gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); +refuse: + 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); } static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -328,6 +232,7 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) struct osmo_scu_prim *scu_prim = NULL; struct msgb *msg = NULL; int rc; + enum handover_result ho_result; switch (event) { case GSCON_EV_A_CONN_REQ: @@ -346,8 +251,10 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) * using T3210 (20s), T3220 (5s) or T3230 (10s) */ conn_fsm_state_chg(ST_WAIT_CC); } + gscon_update_id(conn); break; case GSCON_EV_A_CONN_IND: + gscon_update_id(conn); scu_prim = data; if (!conn->sccp.msc) { LOGPFSML(fi, LOGL_NOTICE, "N-CONNECT.ind from unknown MSC %s\n", @@ -355,18 +262,37 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) 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); + return; } /* 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; + handle_bssap_n_connect(fi, scu_prim); + break; + case GSCON_EV_HANDOVER_END: + ho_result = HO_RESULT_ERROR; + if (data) + ho_result = *(enum handover_result*)data; + LOGPFSML(fi, LOGL_DEBUG, "Handover result: %s\n", handover_result_name(ho_result)); + if (ho_result == HO_RESULT_OK) { + /* In this case the ho struct should still be populated. */ + if (conn->ho.scope & HO_INTER_BSC_IN) { + /* Done with establishing a conn where we accept another BSC's MS via + * inter-BSC handover */ + + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + gscon_dtap_queue_flush(conn, 1); + return; + } + LOG_HO(conn, LOGL_ERROR, + "Conn is in state %s, the only accepted handover kind is inter-BSC MT\n", + osmo_fsm_inst_state_name(conn->fi)); + } + gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, 999); + return; default: OSMO_ASSERT(false); - break; } } @@ -384,481 +310,240 @@ static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *da break; default: OSMO_ASSERT(false); - break; } } -static const char *get_mgw_ep_name(struct gsm_subscriber_connection *conn) -{ - static char ep_name[256]; - struct bsc_msc_data *msc = conn->sccp.msc; - - switch (conn->sccp.msc->a.asp_proto) { - case OSMO_SS7_ASP_PROT_IPA: - /* derive endpoint name from CIC on A interface side */ - snprintf(ep_name, sizeof(ep_name), "%x@mgw", - mgcp_port_to_cic(conn->user_plane.rtp_port, msc->rtp_base)); - break; - default: - /* use dynamic RTPBRIDGE endpoint allocation in MGW */ - osmo_strlcpy(ep_name, ENDPOINT_ID, sizeof(ep_name)); - break; - } - return ep_name; -} - -#define assignment_failed(fi, cause) \ - _assignment_failed(fi, cause, __FILE__, __LINE__) -static void _assignment_failed(struct osmo_fsm_inst *fi, enum gsm0808_cause cause, - const char *file, int line) +static void gscon_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *resp = NULL; - - LOGPFSMLSRC(fi, LOGL_ERROR, file, line, "Assignment failed: %s\n", gsm0808_cause_name(cause)); - - resp = gsm0808_create_assignment_failure(cause, NULL); - sigtran_send(conn, resp, fi); - if (fi->state != ST_ACTIVE) - conn_fsm_state_chg(ST_ACTIVE); + if (!conn->lchan) + gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); } /* 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; + struct gsm_bts *bts; 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) */ - - 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)); - bsc_subscr_pick_codec(&conn_peer, conn); - conn_peer.call_id = conn->sccp.conn_id; - conn_peer.ptime = 20; - osmo_strlcpy(conn_peer.endpoint, get_mgw_ep_name(conn), sizeof(conn_peer.endpoint)); - - /* (Pre)Change state and create the connection */ - conn_fsm_state_chg(ST_WAIT_CRCX_BTS); - 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) { - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - 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.chan_mode, - conn->user_plane.full_rate); - - if (rc == 1) { - send_ass_compl(conn->lchan, fi, false); - return; - } else if (rc != 0) { - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - return; - } - conn_fsm_state_chg(ST_WAIT_ASS_CMPL); - 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! chan_mode=%s full_rate=%d\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 */ - assignment_failed(fi, GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP); - 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); - conn_fsm_state_chg(ST_CLEARING); + case GSCON_EV_ASSIGNMENT_START: + bts = conn->lchan? conn->lchan->ts->trx->bts : NULL; + + if (!bts) { + LOGPFSML(fi, LOGL_ERROR, "Cannot do assignment, no active BTS\n"); 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). */ - conn_fsm_state_chg(ST_WAIT_HO_COMPL); - break; - case GSCON_EV_A_HO_REQ: - /* FIXME: reject any handover requests with HO FAIL until implemented */ + /* Rely on assignment_fsm timeout */ + osmo_fsm_inst_state_chg(fi, ST_ASSIGNMENT, 0, 0); + assignment_fsm_start(conn, bts, data); + return; + + case GSCON_EV_HANDOVER_START: + rate_ctr_inc(&conn->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]); + /* Rely on handover_fsm timeout */ + if (osmo_fsm_inst_state_chg(fi, ST_HANDOVER, 0, 0)) + LOGPFSML(fi, LOGL_ERROR, "Cannot transition to HANDOVER state, discarding\n"); + else + handover_start(data); break; + case GSCON_EV_MO_DTAP: forward_dtap(conn, (struct msgb *)data, fi); break; case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); + submit_dtap(conn, (struct msgb *)data); break; case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); + gscon_sigtran_send(conn, (struct msgb *)data); 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) +static void gscon_fsm_assignment(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = fi->priv; - struct mgcp_conn_peer *conn_peer = 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, otherwise we - * can not proceed. */ - if (strlen(conn_peer->endpoint) <= 0) { - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - 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.chan_mode, conn->user_plane.full_rate); - if (rc != 0) { - assignment_failed(fi, GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE); - return; - } + case GSCON_EV_ASSIGNMENT_END: + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + gscon_dtap_queue_flush(conn, 1); + return; - conn_fsm_state_chg(ST_WAIT_ASS_CMPL); - break; case GSCON_EV_MO_DTAP: forward_dtap(conn, (struct msgb *)data, fi); break; case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); + submit_dtap(conn, (struct msgb *)data); break; case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); + gscon_sigtran_send(conn, (struct msgb *)data); 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) +static void gscon_fsm_handover(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; - 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)); - bsc_subscr_pick_codec(&conn_peer, conn); - 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; - conn_peer.ptime = 20; - - /* (Pre)Change state and modify the connection */ - conn_fsm_state_chg(ST_WAIT_MDCX_BTS); - rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); - if (rc != 0) { - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - return; - } - break; - case GSM48_CMODE_SIGN: - /* Confirm the successful assignment on BSSMAP and - * change back into active state */ - send_ass_compl(lchan, fi, false); - conn_fsm_state_chg(ST_ACTIVE); - 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; - } + case GSCON_EV_HANDOVER_END: + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + gscon_dtap_queue_flush(conn, 1); + return; - break; - case GSCON_EV_RR_ASS_FAIL: - { - enum gsm0808_cause cause = GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE; - if (data) - cause = *((enum gsm0808_cause*)data); - assignment_failed(fi, cause); - } - break; case GSCON_EV_MO_DTAP: forward_dtap(conn, (struct msgb *)data, fi); break; case GSCON_EV_MT_DTAP: - submit_dtap(conn, (struct msgb *)data, fi); + /* cache until handover is done */ + submit_dtap(conn, (struct msgb *)data); break; case GSCON_EV_TX_SCCP: - sigtran_send(conn, (struct msgb *)data, fi); + gscon_sigtran_send(conn, (struct msgb *)data); 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) +static bool same_mgw_info(const struct mgcp_conn_peer *a, const struct mgcp_conn_peer *b) { - struct gsm_subscriber_connection *conn = fi->priv; - struct mgcp_conn_peer conn_peer; - struct sockaddr_in *sin = NULL; + if (!a || !b) + return false; + if (a == b) + return true; + if (strcmp(a->addr, b->addr)) + return false; + if (a->port != b->port) + return false; + if (a->call_id != b->call_id) + return false; + return true; +} - 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)); - bsc_subscr_pick_codec(&conn_peer, conn); - 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)); - conn_peer.ptime = 20; - - /* 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)); - - switch (conn->sccp.msc->a.asp_proto) { - case OSMO_SS7_ASP_PROT_IPA: - /* Send assignment complete message to the MSC */ - send_ass_compl(conn->lchan, fi, true); - conn_fsm_state_chg(ST_ACTIVE); - break; - default: - /* (Pre)Change state and create the connection */ - conn_fsm_state_chg(ST_WAIT_CRCX_MSC); - 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_msc) { - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - return; - } - break; - } +/* Make sure a conn->user_plane.mgw_endpoint is allocated with the proper mgw endpoint name. For + * SCCPlite, pass in msc_assigned_cic the CIC received upon BSSMAP Assignment Command or BSSMAP Handover + * Request form the MSC (which is only stored in conn->user_plane after success). Ignored for AoIP. */ +struct mgw_endpoint *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection *conn, + uint16_t msc_assigned_cic) +{ + if (conn->user_plane.mgw_endpoint) + return conn->user_plane.mgw_endpoint; - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, 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; + if (gscon_is_sccplite(conn)) { + /* derive endpoint name from CIC on A interface side */ + conn->user_plane.mgw_endpoint = + mgw_endpoint_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, + conn->network->mgw.client, conn->fi->id, + "%x@mgw", msc_assigned_cic); + LOGPFSML(conn->fi, LOGL_DEBUG, "MGW endpoint name derived from CIC 0x%x: %s\n", + msc_assigned_cic, mgw_endpoint_name(conn->user_plane.mgw_endpoint)); + + } else if (gscon_is_aoip(conn)) { + /* use dynamic RTPBRIDGE endpoint allocation in MGW */ + conn->user_plane.mgw_endpoint = + mgw_endpoint_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, + conn->network->mgw.client, conn->fi->id, + "rtpbridge/*@mgw"); + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "Conn is neither SCCPlite nor AoIP!?\n"); + return NULL; } + + return conn->user_plane.mgw_endpoint; } -static void gscon_fsm_wait_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, + struct gsm_lchan *for_lchan, + const char *addr, uint16_t port, + struct osmo_fsm_inst *notify, + uint32_t event_success, uint32_t event_failure, + void *notify_data, + struct mgwep_ci **created_ci) { - 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; + int rc; + struct mgwep_ci *ci; + struct mgcp_conn_peer mgw_info; + enum mgcp_verb verb; - /* 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); + if (created_ci) + *created_ci = NULL; - /* Send assignment complete message to the MSC */ - send_ass_compl(lchan, fi, true); + if (gscon_is_sccplite(conn)) { + /* SCCPlite connection uses an MGW endpoint created by the MSC, so there is nothing to do + * here. */ + if (notify) + osmo_fsm_inst_dispatch(notify, event_success, notify_data); + return true; + } - conn_fsm_state_chg(ST_ACTIVE); + mgw_info = (struct mgcp_conn_peer){ + .port = port, + .call_id = conn->sccp.conn_id, + .ptime = 20, + }; + mgcp_pick_codec(&mgw_info, for_lchan); - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, 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; + rc = osmo_strlcpy(mgw_info.addr, addr, sizeof(mgw_info.addr)); + if (rc <= 0 || rc >= sizeof(mgw_info.addr)) { + LOGPFSML(conn->fi, LOGL_ERROR, "Failed to compose MGW endpoint address for MGW -> MSC\n"); + return false; } -} -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; + ci = conn->user_plane.mgw_endpoint_ci_msc; + if (ci) { + const struct mgcp_conn_peer *prev_crcx_info = mgwep_ci_get_rtp_info(ci); - switch (event) { - case GSCON_EV_RSL_CLEAR_COMPL: - resp = gsm0808_create_clear_complete(); - sigtran_send(conn, resp, fi); - /* we cannot terminate the FSM here, as that would send N-DISCCONNET.req - * and 3GPP TS 48.006 Section 9.2 clearly states that SCCP connections must - * always be released from the MSC side*/ - break; - default: - OSMO_ASSERT(false); - break; - } -} + if (!conn->user_plane.mgw_endpoint) { + LOGPFSML(conn->fi, LOGL_ERROR, "Internal error: conn has a CI but no endoint\n"); + return false; + } -/* 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; + if (!prev_crcx_info) { + LOGPFSML(conn->fi, LOGL_ERROR, "There already is an MGW connection for the MSC side," + " but it seems to be broken. Will not CRCX another one (%s)\n", + mgwep_ci_name(ci)); + return false; + } - 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)); - bsc_subscr_pick_codec(&conn_peer, conn); - 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; - conn_peer.ptime = 20; - - /* (Pre)Change state and modify the connection */ - conn_fsm_state_chg(ST_WAIT_MDCX_BTS_HO); - 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); - conn_fsm_state_chg(ST_CLEARING); - return; + if (same_mgw_info(&mgw_info, prev_crcx_info)) { + LOGPFSML(conn->fi, LOGL_DEBUG, + "MSC side MGW endpoint ci is already configured to %s\n", + mgwep_ci_name(ci)); + return true; } - 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 */ - conn_fsm_state_chg(ST_ACTIVE); - 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; + verb = MGCP_VERB_MDCX; + } else + verb = MGCP_VERB_CRCX; - 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. */ - conn_fsm_state_chg(ST_ACTIVE); - break; - case GSCON_EV_MO_DTAP: - forward_dtap(conn, (struct msgb *)data, 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; + gscon_ensure_mgw_endpoint(conn, for_lchan->activate.msc_assigned_cic); + + if (!conn->user_plane.mgw_endpoint) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to allocate endpoint info\n"); + return false; + } + + if (!ci) { + ci = mgw_endpoint_ci_add(conn->user_plane.mgw_endpoint, "to-MSC"); + if (created_ci) + *created_ci = ci; + conn->user_plane.mgw_endpoint_ci_msc = ci; } + if (!ci) { + LOGPFSML(conn->fi, LOGL_ERROR, "Unable to allocate endpoint CI info\n"); + return false; + } + + mgw_endpoint_ci_request(ci, verb, &mgw_info, notify, event_success, event_failure, notify_data); + return true; } #define EV_TRANSPARENT_SCCP S(GSCON_EV_TX_SCCP) | S(GSCON_EV_MO_DTAP) | S(GSCON_EV_MT_DTAP) @@ -866,8 +551,9 @@ static void gscon_fsm_wait_mdcx_bts_ho(struct osmo_fsm_inst *fi, uint32_t event, static const struct osmo_fsm_state gscon_fsm_states[] = { [ST_INIT] = { .name = "INIT", - .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND), - .out_state_mask = S(ST_WAIT_CC), + .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND) + | S(GSCON_EV_HANDOVER_END), + .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_CLEARING), .action = gscon_fsm_init, }, [ST_WAIT_CC] = { @@ -878,133 +564,135 @@ static const struct osmo_fsm_state gscon_fsm_states[] = { }, [ST_ACTIVE] = { .name = "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_MO_HO_CMD) | S(ST_WAIT_HO_COMPL), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_START) | + S(GSCON_EV_HANDOVER_START), + .out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) | + S(ST_HANDOVER), + .onenter = gscon_fsm_active_onenter, .action = gscon_fsm_active, }, - [ST_WAIT_CRCX_BTS] = { - .name = "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_ASSIGNMENT] = { + .name = "ASSIGNMENT", + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_END), + .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .action = gscon_fsm_assignment, }, - [ST_WAIT_ASS_CMPL] = { - .name = "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 = "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 = "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_HANDOVER] = { + .name = "HANDOVER", + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_HANDOVER_END), + .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .action = gscon_fsm_handover, }, [ST_CLEARING] = { .name = "CLEARING", - .in_event_mask = S(GSCON_EV_RSL_CLEAR_COMPL), - .action = gscon_fsm_clearing, + /* dead end state */ }, +}; - /* 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 = "WAIT_MT_HO_ACC", - }, - [ST_WAIT_MT_HO_COMPL] = { - .name = "WAIT_MT_HO_COMPL", - }, - [ST_WAIT_MO_HO_CMD] = { - .name = "WAIT_MO_HO_CMD", - }, - [ST_MO_HO_PROCEEDING] = { - .name = "MO_HO_PROCEEDING", - }, +void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan) +{ + /* On release, do not receive release events that look like the primary lchan is gone. */ + struct gsm_lchan *old_lchan = conn->lchan; - /* Internal handover */ - [ST_WAIT_HO_COMPL] = { - .name = "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) | S(ST_CLEARING), - .action = gscon_fsm_wait_ho_compl, - }, - [ST_WAIT_MDCX_BTS_HO] = { - .name = "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), - }, -}; + conn->lchan = new_lchan; + conn->lchan->conn = conn; + + if (old_lchan) { + lchan_forget_conn(old_lchan); + lchan_release(old_lchan, false, false, 0); + } +} + +void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan) +{ + if (!lchan) + return; + if (conn->assignment.new_lchan == lchan) { + if (conn->assignment.fi) + osmo_fsm_inst_dispatch(conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan); + lchan_forget_conn(conn->assignment.new_lchan); + conn->assignment.new_lchan = NULL; + } + if (conn->ho.new_lchan == lchan) { + if (conn->ho.fi) + osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_LCHAN_ERROR, lchan); + } + if (conn->lchan == lchan) { + lchan_forget_conn(conn->lchan); + conn->lchan = NULL; + } + if (!conn->lchan) { + osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, 999); + gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); + } +} + +/* An lchan was deallocated. */ +void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan) +{ + if (!lchan) + return; + if (conn->assignment.new_lchan == lchan) + conn->assignment.new_lchan = NULL; + if (conn->ho.new_lchan == lchan) + conn->ho.new_lchan = NULL; + if (conn->lchan == lchan) + conn->lchan = NULL; + if (!conn->lchan) + gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); +} + +static void gscon_forget_mgw_endpoint(struct gsm_subscriber_connection *conn) +{ + conn->user_plane.mgw_endpoint = NULL; + conn->user_plane.mgw_endpoint_ci_msc = NULL; + lchan_forget_mgw_endpoint(conn->lchan); + lchan_forget_mgw_endpoint(conn->assignment.new_lchan); + lchan_forget_mgw_endpoint(conn->ho.new_lchan); +} + +void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct mgwep_ci *ci) +{ + if (ci != conn->user_plane.mgw_endpoint_ci_msc) + return; + conn->user_plane.mgw_endpoint_ci_msc = NULL; +} 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) - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - break; case GSCON_EV_A_CLEAR_CMD: /* MSC tells us to cleanly shut down */ - conn_fsm_state_chg(ST_CLEARING); - gsm0808_clear(conn); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, 999); + LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) after BSSMAP Clear Command\n"); + gscon_release_lchans(conn, true); /* 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); + mgw_endpoint_clear(conn->user_plane.mgw_endpoint); - /* 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); + gscon_sigtran_send(conn, gsm0808_create_clear_complete()); 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); + case GSCON_EV_FORGET_MGW_ENDPOINT: + gscon_forget_mgw_endpoint(conn); 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); + if (conn->lchan) { + conn->lchan->release_in_error = true; + conn->lchan->rsl_error_cause = data ? *(uint8_t*)data : RSL_ERR_IE_ERROR; + } + gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); break; case GSCON_EV_MGW_MDCX_RESP_MSC: LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n"); @@ -1021,22 +709,9 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau { 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; - } + lchan_forget_conn(conn->lchan); + lchan_forget_conn(conn->assignment.new_lchan); + lchan_forget_conn(conn->ho.new_lchan); if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) { LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n"); @@ -1046,11 +721,6 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau conn->sccp.state = SUBSCR_SCCP_ST_NONE; } - /* drop pending messages */ - gscon_dtap_queue_flush(conn, 0); - - penalty_timers_free(&conn->hodec2.penalty_timers); - if (conn->bsub) { LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n"); bsc_subscr_put(conn->bsub); @@ -1059,21 +729,27 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau 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); + mgw_endpoint_clear(conn->user_plane.mgw_endpoint); if (conn->lcls.fi) { /* request termination of LCLS FSM */ osmo_fsm_inst_term(conn->lcls.fi, cause, NULL); conn->lcls.fi = NULL; } + + LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) because this conn is terminating\n"); + gscon_release_lchans(conn, false); + + /* drop pending messages */ + gscon_dtap_queue_flush(conn, 0); + + penalty_timers_free(&conn->hodec2.penalty_timers); } static int gscon_timer_cb(struct osmo_fsm_inst *fi) @@ -1082,6 +758,8 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi) switch (fi->T) { case 993210: + lchan_release(conn->lchan, false, true, RSL_ERR_INTERWORKING); + /* MSC has not responded/confirmed connection with CC, this * could indicate a bad SCCP connection. We now inform the the * FSM that controls the BSSMAP reset about the event. Maybe @@ -1093,16 +771,16 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi) * gscon_cleanup() above) */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); break; - case 10: /* Assignment Failed */ - assignment_failed(fi, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); - break; - case MGCP_MGW_TIMEOUT_TIMER_NR: /* Assignment failed (no response from MGW) */ - assignment_failed(fi, GSM0808_CAUSE_EQUIPMENT_FAILURE); - break; - case MGCP_MGW_HO_TIMEOUT_TIMER_NR: /* Handover failed (no response from MGW) */ - conn_fsm_state_chg(ST_ACTIVE); + case 999: + /* The MSC has sent a BSSMAP Clear Command, we acknowledged that, but the conn was never + * disconnected. */ + LOGPFSML(fi, LOGL_ERROR, "Long after a BSSMAP Clear Command, the conn is still not" + " released. For sanity, discarding this conn now.\n"); + a_reset_conn_fail(conn->sccp.msc->a.reset_fsm); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); break; default: + LOGPFSML(fi, LOGL_ERROR, "Unknown timer %d expired\n", fi->T); OSMO_ASSERT(false); } return 0; @@ -1113,8 +791,9 @@ static struct osmo_fsm gscon_fsm = { .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) | - S(GSCON_EV_MGW_MDCX_RESP_MSC) | S(GSCON_EV_LCLS_FAIL), + S(GSCON_EV_LCLS_FAIL) | + S(GSCON_EV_FORGET_LCHAN) | + S(GSCON_EV_FORGET_MGW_ENDPOINT), .allstate_action = gscon_fsm_allstate, .cleanup = gscon_cleanup, .pre_term = gscon_pre_term, @@ -1123,17 +802,16 @@ static struct osmo_fsm gscon_fsm = { .event_names = gscon_fsm_event_names, }; +void bsc_subscr_conn_fsm_init() +{ + OSMO_ASSERT(osmo_fsm_register(&gscon_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&lcls_fsm) == 0); +} + /* 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); - osmo_fsm_register(&lcls_fsm); - g_initialized = true; - } conn = talloc_zero(net, struct gsm_subscriber_connection); if (!conn) @@ -1190,25 +868,20 @@ static void gscon_dtap_queue_add(struct gsm_subscriber_connection *conn, struct msgb_enqueue(&conn->dtap_queue, msg); } -void gscon_dtap_queue_flush(struct gsm_subscriber_connection *conn, int send) +static void gscon_dtap_queue_flush(struct gsm_subscriber_connection *conn, int send) { struct msgb *msg; unsigned int flushed_count = 0; - if (conn->secondary_lchan || conn->ho) { - LOGP(DMSC, LOGL_ERROR, "%s: Cannot send queued DTAP messages, handover/assignment is still ongoing\n", - bsc_subscr_name(conn->bsub)); - send = 0; - } - while ((msg = msgb_dequeue(&conn->dtap_queue))) { conn->dtap_queue_len --; flushed_count ++; if (send) { int link_id = (int)msg->cb[GSCON_DTAP_QUEUE_MSGB_CB_LINK_ID]; bool allow_sacch = !!msg->cb[GSCON_DTAP_QUEUE_MSGB_CB_ALLOW_SACCH]; - LOGP(DMSC, LOGL_DEBUG, "%s: Sending queued DTAP message after handover/assignment (%u/%u)\n", - bsc_subscr_name(conn->bsub), flushed_count, conn->dtap_queue_len); + LOGPFSML(conn->fi, LOGL_DEBUG, + "%s: Sending queued DTAP message after handover/assignment (%u/%u)\n", + bsc_subscr_name(conn->bsub), flushed_count, conn->dtap_queue_len); gsm0808_send_rsl_dtap(conn, msg, link_id, allow_sacch); } else msgb_free(msg); @@ -1290,7 +963,7 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn, failed_to_send: LOGPFSML(conn->fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n"); resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); - sigtran_send(conn, resp, conn->fi); + gscon_sigtran_send(conn, resp); osmo_fsm_inst_state_chg(conn->fi, ST_ACTIVE, 0, 0); } @@ -1298,10 +971,48 @@ void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id, int allow_sacch) { /* buffer message during assignment / handover */ - if (conn->secondary_lchan || conn->ho) { + if (conn->fi->state != ST_ACTIVE) { gscon_dtap_queue_add(conn, msg, link_id, !! allow_sacch); return; } gsm0808_send_rsl_dtap(conn, msg, link_id, allow_sacch); } + +/* Compose an FSM ID, if possible from the current subscriber information */ +void gscon_update_id(struct gsm_subscriber_connection *conn) +{ + osmo_fsm_inst_update_id_f(conn->fi, "conn%u%s%s", + conn->sccp.conn_id, + conn->bsub? "_" : "", + conn->bsub? bsc_subscr_id(conn->bsub) : ""); +} + +bool gscon_is_aoip(struct gsm_subscriber_connection *conn) +{ + if (!conn || !conn->sccp.msc) + return false; + + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_SUA: + case OSMO_SS7_ASP_PROT_M3UA: + return true; + + default: + return false; + } +} + +bool gscon_is_sccplite(struct gsm_subscriber_connection *conn) +{ + if (!conn || !conn->sccp.msc) + return false; + + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + return true; + + default: + return false; + } +} |