diff options
Diffstat (limited to 'src/osmo-bsc/bsc_subscr_conn_fsm.c')
-rw-r--r-- | src/osmo-bsc/bsc_subscr_conn_fsm.c | 508 |
1 files changed, 367 insertions, 141 deletions
diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c index ed08e86ad..ebfbe82a2 100644 --- a/src/osmo-bsc/bsc_subscr_conn_fsm.c +++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c @@ -32,6 +32,7 @@ #include <osmocom/bsc/handover_fsm.h> #include <osmocom/bsc/lchan_fsm.h> #include <osmocom/bsc/lchan_rtp_fsm.h> +#include <osmocom/bsc/lchan.h> #include <osmocom/bsc/bsc_subscriber.h> #include <osmocom/bsc/osmo_bsc_sigtran.h> #include <osmocom/bsc/osmo_bsc_lcls.h> @@ -45,6 +46,7 @@ #include <osmocom/bsc/assignment_fsm.h> #include <osmocom/bsc/codec_pref.h> #include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> +#include <osmocom/mgcp_client/mgcp_client_pool.h> #include <osmocom/core/byteswap.h> #include <osmocom/bsc/lb.h> #include <osmocom/bsc/lcs_loc_req.h> @@ -59,6 +61,8 @@ enum gscon_fsm_states { ST_INIT, + /* wait for initial BSSMAP after the MSC opened a new SCCP connection */ + ST_WAIT_INITIAL_USER_DATA, /* waiting for CC from MSC */ ST_WAIT_CC, /* active connection */ @@ -66,11 +70,13 @@ enum gscon_fsm_states { ST_ASSIGNMENT, ST_HANDOVER, /* BSSMAP CLEAR has been received */ - ST_CLEARING, + ST_WAIT_CLEAR_CMD, + ST_WAIT_SCCP_RLSD, }; static const struct value_string gscon_fsm_event_names[] = { {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, + {GSCON_EV_A_INITIAL_USER_DATA, "A_INITIAL_USER_DATA"}, {GSCON_EV_MO_COMPL_L3, "MO_COMPL_L3"}, {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, @@ -93,8 +99,10 @@ static const struct value_string gscon_fsm_event_names[] = { }; struct osmo_tdef_state_timeout conn_fsm_timeouts[32] = { + [ST_WAIT_INITIAL_USER_DATA] = { .T = -25 }, [ST_WAIT_CC] = { .T = -3210 }, - [ST_CLEARING] = { .T = -4 }, + [ST_WAIT_CLEAR_CMD] = { .T = -4 }, + [ST_WAIT_SCCP_RLSD] = { .T = -4 }, }; /* Transition to a state, using the T timer defined in conn_fsm_timeouts. @@ -137,34 +145,79 @@ int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg) return rc; } -static void gscon_bssmap_clear(struct gsm_subscriber_connection *conn, - enum gsm0808_cause cause) +void gscon_bssmap_clear(struct gsm_subscriber_connection *conn, enum gsm0808_cause cause) { + /* already clearing? */ + switch (conn->fi->state) { + case ST_WAIT_CLEAR_CMD: + case ST_WAIT_SCCP_RLSD: + return; + default: + break; + } + + conn->clear_cause = cause; + conn_fsm_state_chg(ST_WAIT_CLEAR_CMD); +} +static void gscon_fsm_wait_clear_cmd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ struct msgb *resp; int rc; - - if (conn->rx_clear_command) { - LOGPFSML(conn->fi, LOGL_DEBUG, "Not sending BSSMAP CLEAR REQUEST, already got CLEAR COMMAND from MSC\n"); - return; - } + struct gsm_subscriber_connection *conn = fi->priv; + enum gsm0808_cause cause = conn->clear_cause; if (!conn->sccp.msc) { - LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n"); - return; + LOGPFSML(fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n"); + goto nothing_sent; } - LOGPFSML(conn->fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST(%s) to MSC\n", gsm0808_cause_name(cause)); + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST(%s) to MSC\n", gsm0808_cause_name(cause)); resp = gsm0808_create_clear_rqst(cause); if (!resp) { - LOGPFSML(conn->fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n"); - return; + LOGPFSML(fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n"); + goto nothing_sent; } - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST)); rc = osmo_bsc_sigtran_send(conn, resp); - if (rc < 0) + if (rc < 0) { LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message\n"); + goto nothing_sent; + } + return; + +nothing_sent: + /* Normally, we request a CLEAR from the MSC and terminate as soon as the CLEAR COMMAND has been issued by the + * MSC. But if we are trying to clear without being able to send anything to the MSC, we might as well shut down + * the conn right away now. */ + conn_fsm_state_chg(ST_WAIT_SCCP_RLSD); +} + +void gscon_fsm_wait_sccp_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* 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" */ + if (!gscon_sigtran_send(conn, gsm0808_create_clear_complete())) + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE)); + + /* Give the handover_fsm a chance to book this as handover success before tearing down everything, + * making it look like a sudden death failure. */ + if (conn->ho.fi) + osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL); + + if (conn->lcs.loc_req) + osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL); + + gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(conn->clear_cause)); + osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint); + + /* If there is no SCCP connection at all, then no need to wait for an SCCP RLSD. */ + if (!conn->sccp.msc || conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED) + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } /* forward MO DTAP from RSL side to BSSAP side */ @@ -176,7 +229,7 @@ static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *ms OSMO_ASSERT(conn); resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_DTAP]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_DTAP)); gscon_sigtran_send(conn, resp); } @@ -195,7 +248,8 @@ static void gscon_release_lchan(struct gsm_subscriber_connection *conn, struct g conn->ho.new_lchan = NULL; if (conn->assignment.new_lchan == lchan) conn->assignment.new_lchan = NULL; - lchan_release(lchan, do_rr_release, err, cause_rr); + lchan_release(lchan, do_rr_release, err, cause_rr, + gscon_last_eutran_plmn(conn)); } void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release, enum gsm48_rr_cause cause_rr) @@ -208,89 +262,133 @@ void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_rel gscon_release_lchan(conn, conn->lchan, do_rr_release, false, cause_rr); } -static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim) +static int validate_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg) { - struct gsm_subscriber_connection *conn = fi->priv; - struct msgb *msg = scu_prim->oph.msg; struct bssmap_header *bs; - uint8_t bssmap_type; + enum BSS_MAP_MSG_TYPE bssmap_type; msg->l3h = msgb_l2(msg); if (!msgb_l3(msg)) { LOGPFSML(fi, LOGL_ERROR, "internal error: no l3 in msg\n"); - goto refuse; + return -EINVAL; } if (msgb_l3len(msg) < sizeof(*bs)) { - LOGPFSML(fi, LOGL_NOTICE, "message too short for BSSMAP header (%u < %zu)\n", + LOGPFSML(fi, LOGL_ERROR, "message too short for BSSMAP header (%u < %zu)\n", msgb_l3len(msg), sizeof(*bs)); - goto refuse; + return -EINVAL; } bs = (struct bssmap_header*)msgb_l3(msg); if (msgb_l3len(msg) < (bs->length + sizeof(*bs))) { - LOGPFSML(fi, LOGL_NOTICE, + LOGPFSML(fi, LOGL_ERROR, "message too short for length indicated in BSSMAP header (%u < %u)\n", msgb_l3len(msg), bs->length); - goto refuse; + return -EINVAL; } 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; + LOGPFSML(fi, LOGL_ERROR, + "message type not allowed for initial BSSMAP: %s\n", gsm0808_bssap_name(bs->type)); + return -EINVAL; } msg->l4h = &msg->l3h[sizeof(*bs)]; - bssmap_type = msg->l4h[0]; - - LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type), - gsm0808_bssmap_name(bssmap_type)); + /* Validate initial message type. See also BSC_Tests.TC_outbound_connect. */ + bssmap_type = msg->l4h[0]; switch (bssmap_type) { case BSS_MAP_MSG_HANDOVER_RQST: case BSS_MAP_MSG_PERFORM_LOCATION_RQST: - break; + return 0; default: - LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n", + LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); - goto refuse; + return -EINVAL; } +} - /* First off, accept the new conn. */ - if (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)) { - LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n"); - goto refuse; - } +static void handle_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct bssmap_header *bs; + enum BSS_MAP_MSG_TYPE bssmap_type; - /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ - conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + /* validate_initial_user_data() must be called before this */ + OSMO_ASSERT(msgb_l4(msg)); + + bs = msgb_l3(msg); + bssmap_type = msg->l4h[0]; + + /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() (OS#2969) */ + + LOGPFSML(fi, LOGL_DEBUG, "Rx initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type), + gsm0808_bssmap_name(bssmap_type)); switch (bssmap_type) { case BSS_MAP_MSG_HANDOVER_RQST: - /* Inter-BSC MT Handover Request, another BSS is handovering to us. */ + rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST]); + /* Inter-BSC incoming Handover Request, another BSS is handovering to us. */ handover_start_inter_bsc_in(conn, msg); return; case BSS_MAP_MSG_PERFORM_LOCATION_RQST: + rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST]); /* Location Services: MSC asks for location of an IDLE subscriber */ conn_fsm_state_chg(ST_ACTIVE); lcs_loc_req_start(conn, msg); return; default: - OSMO_ASSERT(false); + LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n", + gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type)); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; } +} -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 handle_sccp_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; + + /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */ + conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED; + + msg->l3h = msgb_l2(msg); + + /* If (BSSMAP) user data is included, validate it before accepting the connection */ + if (msgb_l3(msg) && msgb_l3len(msg)) { + if (validate_initial_user_data(fi, msg)) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + } + + /* accept the new conn. */ + if (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)) { + LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + + /* The initial user data may already be included in this N-Connect, or it may come later in a separate message. + * If it is already included, also go to ST_WAIT_INITIAL_USER_DATA now, so that we don't have to tend to two + * separate code paths doing the same thing (handling of HANDOVER_END). */ + OSMO_ASSERT(conn_fsm_state_chg(ST_WAIT_INITIAL_USER_DATA) == 0); + + /* It is usually a bad idea to continue using a fi after a state change, because the fi might terminate during + * the state change. In this case it is certain that the fi stays around for the initial user data. */ + if (msgb_l3(msg) && msgb_l3len(msg)) { + handle_initial_user_data(fi, msg); + } else { + LOGPFSML(fi, LOGL_DEBUG, "N-Connect does not contain user data (no BSSMAP message included)\n"); + } } static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -299,7 +397,6 @@ 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_MO_COMPL_L3: @@ -327,11 +424,28 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) 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) */ + handle_sccp_n_connect(fi, scu_prim); + break; + default: + OSMO_ASSERT(false); + } +} + +static void gscon_fsm_wait_initial_user_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *msg = data; + enum handover_result ho_result; - handle_bssap_n_connect(fi, scu_prim); + switch (event) { + case GSCON_EV_A_INITIAL_USER_DATA: + if (validate_initial_user_data(fi, msg)) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + return; + } + handle_initial_user_data(fi, msg); break; + case GSCON_EV_HANDOVER_END: ho_result = HO_RESULT_ERROR; if (data) @@ -348,12 +462,10 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) return; } LOG_HO(conn, LOGL_ERROR, - "Conn is in state %s, the only accepted handover kind is inter-BSC MT\n", + "Conn is in state %s, the only accepted handover kind is inter-BSC incoming handover\n", osmo_fsm_inst_state_name(conn->fi)); } gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); - if (conn->fi->state != ST_CLEARING) - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, -4); return; default: OSMO_ASSERT(false); @@ -373,7 +485,6 @@ static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *da confirmed connection, then instead simply drop the connection */ LOGPFSML(fi, LOGL_INFO, "Connection confirmed but lchan was dropped previously, clearing conn\n"); - osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, -4); gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); break; } @@ -510,6 +621,62 @@ static bool same_mgw_info(const struct mgcp_conn_peer *a, const struct mgcp_conn return true; } +static struct mgcp_client *select_mgw(struct gsm_subscriber_connection *conn, struct gsm_lchan *for_lchan) +{ + struct mgcp_client_pool_member *mgcp_pmemb; + struct mgcp_client *mgcp_client; + struct gsm_bts *bts = for_lchan->ts->trx->bts; + + /* If BTS is not pinned to a given MGW, let regular allocation which + * spreads load over available MGWs: */ + if (bts->mgw_pool_target == -1) + goto pick_any; + + /* BTS is pinned to an MGW, retrieve pointer to it: */ + mgcp_pmemb = mgcp_client_pool_find_member_by_nr(conn->network->mgw.mgw_pool, bts->mgw_pool_target); + if (!mgcp_pmemb) { + if (!bts->mgw_pool_target_strict) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "mgw pool-target %u not found! selecting another one.\n", bts->mgw_pool_target); + goto pick_any; + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u not found!\n", bts->mgw_pool_target); + return NULL; + } + } + if (mgcp_client_pool_member_is_blocked(mgcp_pmemb)) { + if (!bts->mgw_pool_target_strict) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "mgw pool-target %u is administratively blocked! selecting another one.\n", + bts->mgw_pool_target); + goto pick_any; + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u is administratively blocked!\n", + bts->mgw_pool_target); + return NULL; + } + } + + mgcp_client = mgcp_client_pool_member_get(mgcp_pmemb); + if (!mgcp_client) { + if (!bts->mgw_pool_target_strict) { + LOGPFSML(conn->fi, LOGL_NOTICE, + "mgw pool-target %u is not connected! selecting another one.\n", + bts->mgw_pool_target); + goto pick_any; + } else { + LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u is not connected!\n", + bts->mgw_pool_target); + return NULL; + } + } + return mgcp_client; + +pick_any: + mgcp_client = mgcp_client_pool_get(conn->network->mgw.mgw_pool); + return mgcp_client; +} + /* 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. */ @@ -517,39 +684,57 @@ struct osmo_mgcpc_ep *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection uint16_t msc_assigned_cic, struct gsm_lchan *for_lchan) { const char *epname; + struct mgcp_client *mgcp_client = NULL; + + if (!conn) { + LOG_LCHAN(for_lchan, LOGL_ERROR, "no conn!\n"); + return NULL; + } if (conn->user_plane.mgw_endpoint) return conn->user_plane.mgw_endpoint; + if (gscon_is_sccplite(conn) || gscon_is_aoip(conn)) { + /* Get MGCP client from pool */ + mgcp_client = select_mgw(conn, for_lchan); + if (!mgcp_client) { + LOGPFSML(conn->fi, LOGL_ERROR, + "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n"); + return NULL; + } + } + if (gscon_is_sccplite(conn)) { /* derive endpoint name from CIC on A interface side */ conn->user_plane.mgw_endpoint = osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, - conn->network->mgw.client, + mgcp_client, conn->network->mgw.tdefs, conn->fi->id, "%x@%s", msc_assigned_cic, - mgcp_client_endpoint_domain(conn->network->mgw.client)); + mgcp_client_endpoint_domain(mgcp_client)); LOGPFSML(conn->fi, LOGL_DEBUG, "MGW endpoint name derived from CIC 0x%x: %s\n", msc_assigned_cic, osmo_mgcpc_ep_name(conn->user_plane.mgw_endpoint)); } else if (gscon_is_aoip(conn)) { - if (is_ipaccess_bts(for_lchan->ts->trx->bts)) /* use dynamic RTPBRIDGE endpoint allocation in MGW */ - epname = mgcp_client_rtpbridge_wildcard(conn->network->mgw.client); + epname = mgcp_client_rtpbridge_wildcard(mgcp_client); else { - epname = mgcp_client_e1_epname(conn, conn->network->mgw.client, for_lchan->ts->e1_link.e1_nr, + uint8_t i460_bit_offs; + if (for_lchan->ts->e1_link.e1_ts_ss == E1_SUBSLOT_FULL) + i460_bit_offs = 0; + else + i460_bit_offs = for_lchan->ts->e1_link.e1_ts_ss * 2; + + epname = mgcp_client_e1_epname(conn, mgcp_client, for_lchan->ts->e1_link.e1_nr, for_lchan->ts->e1_link.e1_ts, 16, - for_lchan->ts->e1_link.e1_ts_ss*2); + i460_bit_offs); } conn->user_plane.mgw_endpoint = - osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, - conn->network->mgw.client, - conn->network->mgw.tdefs, - conn->fi->id, - "%s", epname); + osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, mgcp_client, + conn->network->mgw.tdefs, conn->fi->id, "%s", epname); } else { LOGPFSML(conn->fi, LOGL_ERROR, "Conn is neither SCCPlite nor AoIP!?\n"); return NULL; @@ -599,7 +784,7 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, ci = conn->user_plane.mgw_endpoint_ci_msc; if (ci) { - const struct mgcp_conn_peer *prev_crcx_info = osmo_mgcpc_ep_ci_get_rtp_info(ci); + const struct mgcp_conn_peer *prev_crcx_info = osmo_mgcpc_ep_ci_get_remote_rtp_info(ci); if (!conn->user_plane.mgw_endpoint) { LOGPFSML(conn->fi, LOGL_ERROR, "Internal error: conn has a CI but no endpoint\n"); @@ -617,6 +802,8 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, LOGPFSML(conn->fi, LOGL_DEBUG, "MSC side MGW endpoint ci is already configured to %s\n", osmo_mgcpc_ep_ci_name(ci)); + /* Immediately dispatch the success event */ + osmo_fsm_inst_dispatch(notify, event_success, notify_data); return true; } @@ -651,15 +838,25 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn, static const struct osmo_fsm_state gscon_fsm_states[] = { [ST_INIT] = { .name = "INIT", - .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND) - | S(GSCON_EV_HANDOVER_END), - .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_CLEARING), + .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND), + .out_state_mask = 0 + | S(ST_WAIT_INITIAL_USER_DATA) + | S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_init, + }, + [ST_WAIT_INITIAL_USER_DATA] = { + .name = "WAIT_INITIAL_USER_DATA", + .in_event_mask = 0 + | S(GSCON_EV_A_INITIAL_USER_DATA) + | S(GSCON_EV_HANDOVER_END) + , + .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), + .action = gscon_fsm_wait_initial_user_data, }, [ST_WAIT_CC] = { .name = "WAIT_CC", .in_event_mask = S(GSCON_EV_A_CONN_CFM), - .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_wait_cc, }, [ST_ACTIVE] = { @@ -669,26 +866,32 @@ static const struct osmo_fsm_state gscon_fsm_states[] = { | S(GSCON_EV_LCS_LOC_REQ_END) | S(GSCON_EV_MO_COMPL_L3) , - .out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) | + .out_state_mask = S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD) | S(ST_ASSIGNMENT) | S(ST_HANDOVER), .action = gscon_fsm_active, }, [ST_ASSIGNMENT] = { .name = "ASSIGNMENT", .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_END), - .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_assignment, }, [ST_HANDOVER] = { .name = "HANDOVER", .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_HANDOVER_END), - .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD), .action = gscon_fsm_handover, }, - [ST_CLEARING] = { - .name = "CLEARING", - /* dead end state */ - }, + [ST_WAIT_CLEAR_CMD] = { + .name = "WAIT_CLEAR_CMD", + .onenter = gscon_fsm_wait_clear_cmd_onenter, + .out_state_mask = S(ST_WAIT_SCCP_RLSD), + }, + [ST_WAIT_SCCP_RLSD] = { + .name = "WAIT_SCCP_RLSD", + .onenter = gscon_fsm_wait_sccp_rlsd_onenter, + .in_event_mask = S(GSCON_EV_HANDOVER_END), + }, }; void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan) @@ -696,9 +899,19 @@ void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct g /* On release, do not receive release events that look like the primary lchan is gone. */ struct gsm_lchan *old_lchan = conn->lchan; + OSMO_ASSERT(new_lchan); + if (old_lchan == new_lchan) return; + if (!old_lchan) + LOGPFSML(conn->fi, LOGL_DEBUG, "setting primary lchan for this conn to %s\n", + new_lchan->fi? osmo_fsm_inst_name(new_lchan->fi) : gsm_lchan_name(new_lchan)); + else + LOGPFSML(conn->fi, LOGL_DEBUG, "primary lchan for this conn changes from %s to %s\n", + old_lchan->fi? osmo_fsm_inst_name(old_lchan->fi) : gsm_lchan_name(old_lchan), + new_lchan->fi? osmo_fsm_inst_name(new_lchan->fi) : gsm_lchan_name(new_lchan)); + conn->lchan = new_lchan; conn->lchan->conn = conn; @@ -727,19 +940,15 @@ void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lc lchan_forget_conn(conn->lchan); conn->lchan = NULL; } - /* If the conn has no lchan anymore, it was released by the BTS and needs to Clear towards MSC. */ - if (!conn->lchan) { + /* If the conn has no lchan anymore, it was released by the BTS and needs to Clear towards MSC. + * However, if a Location Request is still busy, do not send Clear Request. */ + if (!conn->lchan && !conn->lcs.loc_req) { switch (conn->fi->state) { case ST_WAIT_CC: /* The SCCP connection was not yet confirmed by a CC, the BSSAP is not fully established yet so we cannot release it. First wait for the CC, and release in gscon_fsm_wait_cc(). */ break; default: - /* Ensure that the FSM is in ST_CLEARING. */ - osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, -4); - /* fall thru, omit an error log if already in ST_CLEARING */ - case ST_CLEARING: - /* Request a Clear Command from the MSC. */ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); break; } @@ -778,8 +987,7 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan osmo_fsm_inst_name(conn->fi), detach_label); } - if ((conn->fi && conn->fi->state != ST_CLEARING) - && !conn->lchan + if (!conn->lchan && !conn->ho.new_lchan && !conn->assignment.new_lchan && !conn->lcs.loc_req) @@ -788,6 +996,16 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan static void gscon_forget_mgw_endpoint(struct gsm_subscriber_connection *conn) { + struct mgcp_client *mgcp_client; + + /* Put MGCP client back into MGW pool */ + mgcp_client = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint); + mgcp_client_pool_put(mgcp_client); + + /* Be sure that the endpoint CI we are maintaining in user_plane + * is also removed from the other locations as well. */ + gscon_forget_mgw_endpoint_ci(conn, conn->user_plane.mgw_endpoint_ci_msc); + conn->user_plane.mgw_endpoint = NULL; conn->user_plane.mgw_endpoint_ci_msc = NULL; conn->ho.created_ci_for_msc = NULL; @@ -803,50 +1021,27 @@ void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct if (conn->user_plane.mgw_endpoint_ci_msc == ci) conn->user_plane.mgw_endpoint_ci_msc = NULL; + + if (conn->assignment.created_ci_for_msc == ci) + conn->assignment.created_ci_for_msc = NULL; } static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = fi->priv; - const struct gscon_clear_cmd_data *ccd; - struct osmo_mobile_identity *mi_imsi; + const struct tlv_parsed *tp; + struct osmo_mobile_identity mi_imsi; /* Regular allstate event processing */ switch (event) { case GSCON_EV_A_CLEAR_CMD: - conn->rx_clear_command = true; - - /* Give the handover_fsm a chance to book this as handover success before tearing down everything, - * making it look like a sudden death failure. */ - if (conn->ho.fi) - osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL); - - if (conn->lcs.loc_req) - osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL); - OSMO_ASSERT(data); - ccd = data; - if (conn->lchan) - conn->lchan->release.is_csfb = ccd->is_csfb; - /* MSC tells us to cleanly shut down */ - if (conn->fi->state != ST_CLEARING) - osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, -4); - LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) after BSSMAP Clear Command\n"); - gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(ccd->cause_0808)); - /* 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 */ - osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint); - - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE]); - gscon_sigtran_send(conn, gsm0808_create_clear_complete()); + conn->clear_cause = *(const enum gsm0808_cause *)data; + conn_fsm_state_chg(ST_WAIT_SCCP_RLSD); break; case GSCON_EV_A_DISC_IND: - /* MSC or SIGTRAN network has hard-released SCCP connection, - * terminate the FSM now. */ + /* MSC or SIGTRAN network has hard-released SCCP connection, terminate the FSM now. + * Cleanup is done in gscon_pre_term() and gscon_cleanup(). */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); break; case GSCON_EV_FORGET_MGW_ENDPOINT: @@ -859,7 +1054,9 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d conn->lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(conn->lchan->release.rsl_error_cause); } - gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + /* Request BSSMAP Clear, but do not abort an ongoing Location Request */ + if (!conn->lcs.loc_req) + 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"); @@ -868,14 +1065,26 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d break; case GSCON_EV_A_COMMON_ID_IND: OSMO_ASSERT(data); - mi_imsi = data; + tp = data; + if (osmo_mobile_identity_decode(&mi_imsi, TLVP_VAL(tp, GSM0808_IE_IMSI), TLVP_LEN(tp, GSM0808_IE_IMSI), false) + || mi_imsi.type != GSM_MI_TYPE_IMSI) { + LOGPFSML(fi, LOGL_ERROR, "CommonID: could not parse IMSI\n"); + return; + } if (!conn->bsub) - conn->bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers, mi_imsi->imsi, + conn->bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers, mi_imsi.imsi, BSUB_USE_CONN); else { /* we already have a bsc_subscr associated; maybe that subscriber has no IMSI yet? */ if (!conn->bsub->imsi[0]) - bsc_subscr_set_imsi(conn->bsub, mi_imsi->imsi); + bsc_subscr_set_imsi(conn->bsub, mi_imsi.imsi); + } + if (TLVP_PRESENT(tp, GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID)) { + conn->fast_return.allowed = true; /* Always allowed for CSFB */ + conn->fast_return.last_eutran_plmn_valid = true; + osmo_plmn_from_bcd(TLVP_VAL(tp, GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID), &conn->fast_return.last_eutran_plmn); + LOGPFSML(fi, LOGL_DEBUG, "subscr comes from E-UTRAN PLMN %s\n", + osmo_plmn_name(&conn->fast_return.last_eutran_plmn)); } gscon_update_id(conn); break; @@ -916,8 +1125,21 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_client *mgcp_client; + + /* Put MGCP client back into MGW pool */ + mgcp_client = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint); + mgcp_client_pool_put(mgcp_client); osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint); + conn->user_plane.mgw_endpoint = NULL; + conn->user_plane.mgw_endpoint_ci_msc = NULL; + + if (conn->ho.fi) + osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL); + + if (conn->lcs.loc_req) + osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL); if (conn->lcls.fi) { /* request termination of LCLS FSM */ @@ -926,14 +1148,12 @@ static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause ca } LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) because this conn is terminating\n"); - /* when things go smoothly, the lchan should have been released before FSM instance termination. So if this is - * necessary it's probably "abnormal". */ - gscon_release_lchans(conn, true, GSM48_RR_CAUSE_ABNORMAL_UNSPEC); + gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(conn->clear_cause)); /* drop pending messages */ gscon_dtap_queue_flush(conn, 0); - penalty_timers_free(&conn->hodec2.penalty_timers); + penalty_timers_clear(&conn->hodec2.penalty_timers, NULL); } static int gscon_timer_cb(struct osmo_fsm_inst *fi) @@ -958,8 +1178,9 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi) case -4: /* 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"); + LOGPFSML(fi, LOGL_ERROR, "Long after expecting %s, the conn is still not" + " released. For sanity, discarding this conn now.\n", + fi->state == ST_WAIT_CLEAR_CMD ? "BSSMAP Clear Command" : "SCCP RLSD"); a_reset_conn_fail(conn->sccp.msc); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); break; @@ -987,7 +1208,7 @@ static struct osmo_fsm gscon_fsm = { .event_names = gscon_fsm_event_names, }; -void bsc_subscr_conn_fsm_init() +static __attribute__((constructor)) void bsc_subscr_conn_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&gscon_fsm) == 0); OSMO_ASSERT(osmo_fsm_register(&lcls_fsm) == 0); @@ -1004,9 +1225,12 @@ struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *ne conn->network = net; INIT_LLIST_HEAD(&conn->dtap_queue); - /* BTW, penalty timers will be initialized on-demand. */ + INIT_LLIST_HEAD(&conn->hodec2.penalty_timers); conn->sccp.conn_id = -1; + /* Default clear cause (on RR translates to GSM48_RR_CAUSE_ABNORMAL_UNSPEC) */ + conn->clear_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE; + /* 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_DEBUG, NULL); @@ -1089,19 +1313,23 @@ static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, en switch (rllr_ind) { case BSC_RLLR_IND_EST_CONF: - rsl_data_request(msg, OBSC_LINKID_CB(msg)); + rsl_data_request(msg, link_id); break; case BSC_RLLR_IND_REL_IND: - bsc_sapi_n_reject(lchan->conn, OBSC_LINKID_CB(msg), + bsc_sapi_n_reject(lchan->conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_MS_NOT_EQUIPPED); msgb_free(msg); break; case BSC_RLLR_IND_ERR_IND: case BSC_RLLR_IND_TIMEOUT: - bsc_sapi_n_reject(lchan->conn, OBSC_LINKID_CB(msg), + bsc_sapi_n_reject(lchan->conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_BSS_NOT_EQUIPPED); msgb_free(msg); break; + default: + LOGPLCHAN(lchan, DRLL, LOGL_NOTICE, "Received unknown rllr_ind %u\n", rllr_ind); + msgb_free(msg); + break; } } @@ -1123,7 +1351,6 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn, sapi = link_id & 0x7; msg->lchan = conn->lchan; - msg->dst = msg->lchan->ts->trx->rsl_link; /* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */ if (allow_sacch && sapi != 0) { @@ -1139,7 +1366,7 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn, rc = rll_establish(msg->lchan, sapi, rll_ind_cb, msg); if (rc) { msgb_free(msg); - bsc_sapi_n_reject(conn, link_id, GSM0808_CAUSE_BSS_NOT_EQUIPPED); + bsc_sapi_n_reject(conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_BSS_NOT_EQUIPPED); goto failed_to_send; } return; @@ -1154,7 +1381,6 @@ 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"); gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE); - osmo_fsm_inst_state_chg(conn->fi, ST_ACTIVE, 0, 0); } void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn, |