diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libmsc/gsm_04_08.c | 102 | ||||
-rw-r--r-- | src/libmsc/msc_ifaces.c | 2 | ||||
-rw-r--r-- | src/libmsc/osmo_msc.c | 149 | ||||
-rw-r--r-- | src/libmsc/subscr_conn.c | 429 | ||||
-rw-r--r-- | src/libmsc/transaction.c | 10 |
5 files changed, 363 insertions, 329 deletions
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c index f29c0b68e..b51fd16b5 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -329,7 +329,6 @@ int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) struct osmo_location_area_id old_lai, new_lai; struct osmo_fsm_inst *lu_fsm; bool is_utran; - int rc; lu = (struct gsm48_loc_upd_req *) gh->data; @@ -337,10 +336,21 @@ int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len); - rc = msc_create_conn_fsm(conn, mi_string); - if (rc) - /* logging already happened in msc_create_conn_fsm() */ - return rc; + if (msc_subscr_conn_is_establishing_auth_ciph(conn)) { + LOGP(DMM, LOGL_ERROR, + "Cannot accept another LU, conn already busy establishing authenticity;" + " extraneous LOCATION UPDATING REQUEST: MI(%s)=%s type=%s\n", + gsm48_mi_type_name(mi_type), mi_string, get_value_string(lupd_names, lu->type)); + return -EINVAL; + } + + if (msc_subscr_conn_is_accepted(conn)) { + LOGP(DMM, LOGL_ERROR, + "Cannot accept another LU, conn already established;" + " extraneous LOCATION UPDATING REQUEST: MI(%s)=%s type=%s\n", + gsm48_mi_type_name(mi_type), mi_string, get_value_string(lupd_names, lu->type)); + return -EINVAL; + } msc_subscr_conn_update_id(conn, COMPLETE_LAYER3_LU, mi_string); @@ -420,6 +430,7 @@ int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) return -EIO; } + msc_subscr_conn_complete_layer_3(conn); return 0; } @@ -670,7 +681,11 @@ static int cm_serv_reuse_conn(struct gsm_subscriber_connection *conn, const uint accept_reuse: DEBUGP(DMM, "%s: re-using already accepted connection\n", vlr_subscr_name(conn->vsub)); - conn->received_cm_service_request = true; + + if (!conn->received_cm_service_request) { + conn->received_cm_service_request = true; + msc_subscr_conn_get(conn, MSC_CONN_USE_CM_SERVICE); + } msc_subscr_conn_update_id(conn, conn->complete_layer3_type, mi_string); return conn->network->vlr->ops.tx_cm_serv_acc(conn); } @@ -698,32 +713,31 @@ int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *ms /* unfortunately in Phase1 the classmark2 length is variable */ uint8_t classmark2_len = gh->data[1]; uint8_t *classmark2 = gh->data+2; - uint8_t mi_len = *(classmark2 + classmark2_len); - uint8_t *mi = (classmark2 + classmark2_len + 1); + uint8_t *mi_p = classmark2 + classmark2_len; + uint8_t mi_len = *mi_p; + uint8_t *mi = mi_p + 1; struct osmo_location_area_id lai; bool is_utran; - int rc; lai.plmn = conn->network->plmn; lai.lac = conn->lac; - DEBUGP(DMM, "<- CM SERVICE REQUEST "); if (msg->data_len < sizeof(struct gsm48_service_request*)) { - DEBUGPC(DMM, "wrong sized message\n"); + LOGP(DMM, LOGL_ERROR, "<- CM SERVICE REQUEST wrong sized message\n"); return msc_gsm48_tx_mm_serv_rej(conn, GSM48_REJECT_INCORRECT_MESSAGE); } if (msg->data_len < req->mi_len + 6) { - DEBUGPC(DMM, "does not fit in packet\n"); + LOGP(DMM, LOGL_ERROR, "<- CM SERVICE REQUEST does not fit in packet\n"); return msc_gsm48_tx_mm_serv_rej(conn, GSM48_REJECT_INCORRECT_MESSAGE); } gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); mi_type = mi[0] & GSM_MI_TYPE_MASK; - DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n", - req->cm_service_type, gsm48_mi_type_name(mi_type), mi_string); + DEBUGP(DMM, "<- CM SERVICE REQUEST serv_type=0x%02x MI(%s)=%s\n", + req->cm_service_type, gsm48_mi_type_name(mi_type), mi_string); switch (mi_type) { case GSM_MI_TYPE_IMSI: @@ -754,27 +768,23 @@ int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *ms return msc_gsm48_tx_mm_serv_rej(conn, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED); } - osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len)); - memcpy(conn->classmark.classmark2, classmark2, classmark2_len); - conn->classmark.classmark2_len = classmark2_len; + if (msc_subscr_conn_is_accepted(conn)) + return cm_serv_reuse_conn(conn, mi_p); - if (conn->fi) { - if (msc_subscr_conn_is_accepted(conn)) - return cm_serv_reuse_conn(conn, mi-1); - LOGP(DMM, LOGL_ERROR, "%s: connection already in use\n", - vlr_subscr_name(conn->vsub)); + if (msc_subscr_conn_is_establishing_auth_ciph(conn)) { + LOGP(DMM, LOGL_ERROR, + "Cannot accept CM Service Request, conn already busy establishing authenticity\n"); msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_UNKNOWN_ERROR); return -EINVAL; + /* or should we accept and note down the service request anyway? */ } - rc = msc_create_conn_fsm(conn, mi_string); - if (rc) { - msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_UNKNOWN_ERROR); - /* logging already happened in msc_create_conn_fsm() */ - return rc; - } msc_subscr_conn_update_id(conn, COMPLETE_LAYER3_CM_SERVICE_REQ, mi_string); + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, mi_p); + memcpy(conn->classmark.classmark2, classmark2, classmark2_len); + conn->classmark.classmark2_len = classmark2_len; + is_utran = (conn->via_ran == RAN_UTRAN_IU); vlr_proc_acc_req(conn->fi, SUBSCR_CONN_E_ACCEPTED, SUBSCR_CONN_E_CN_CLOSE, NULL, @@ -785,6 +795,7 @@ int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *ms classmark_is_r99(&conn->classmark), is_utran); + msc_subscr_conn_complete_layer_3(conn); return 0; } @@ -1149,7 +1160,6 @@ static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct m uint8_t *mi_lv; uint8_t mi_type; char mi_string[GSM48_MI_SIZE]; - int rc = 0; struct osmo_location_area_id lai; bool is_utran; @@ -1159,19 +1169,26 @@ static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct m resp = (struct gsm48_pag_resp *) &gh->data[0]; gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh), mi_string, &mi_type); - DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n", - gsm48_mi_type_name(mi_type), mi_string); + + if (msc_subscr_conn_is_establishing_auth_ciph(conn)) { + LOGP(DMM, LOGL_ERROR, + "Ignoring Paging Response, conn already busy establishing authenticity\n"); + return 0; + } + + if (msc_subscr_conn_is_accepted(conn)) { + LOGP(DMM, LOGL_ERROR, "Ignoring Paging Response, conn already established\n"); + return 0; + } + + DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n", gsm48_mi_type_name(mi_type), mi_string); mi_lv = gsm48_cm2_get_mi(classmark2_lv, msgb_l3len(msg) - sizeof(*gh)); if (!mi_lv) { - /* FIXME */ + LOGP(DRR, LOGL_ERROR, "PAGING RESPONSE: invalid Mobile Identity\n"); return -1; } - rc = msc_create_conn_fsm(conn, mi_string); - if (rc) - /* logging already happened in msc_create_conn_fsm() */ - return rc; msc_subscr_conn_update_id(conn, COMPLETE_LAYER3_PAGING_RESP, mi_string); memcpy(conn->classmark.classmark2, classmark2_lv+1, *classmark2_lv); @@ -1187,6 +1204,7 @@ static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct m classmark_is_r99(&conn->classmark), is_utran); + msc_subscr_conn_complete_layer_3(conn); return 0; } @@ -3347,6 +3365,7 @@ void cm_service_request_concludes(struct gsm_subscriber_connection *conn, gsm48_pdisc_msgtype_name(pdisc, msg_type)); } conn->received_cm_service_request = false; + msc_subscr_conn_put(conn, MSC_CONN_USE_CM_SERVICE); } /* TS 24.007 11.2.3.2.3 Message Type Octet / Duplicate Detection */ @@ -3590,7 +3609,7 @@ static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result r { uint8_t cause; struct gsm_subscriber_connection *conn = msc_conn_ref; - conn->received_cm_service_request = false; + int rc; switch (result) { default: @@ -3616,7 +3635,14 @@ static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result r break; }; - return msc_gsm48_tx_mm_serv_rej(conn, cause); + rc = msc_gsm48_tx_mm_serv_rej(conn, cause); + + if (conn->received_cm_service_request) { + conn->received_cm_service_request = false; + msc_subscr_conn_put(conn, MSC_CONN_USE_CM_SERVICE); + } + + return rc; } /* For msc_vlr_set_ciph_mode() */ diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c index b2606b6d2..b76cdb45d 100644 --- a/src/libmsc/msc_ifaces.c +++ b/src/libmsc/msc_ifaces.c @@ -114,8 +114,6 @@ int msc_gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, if (!conn) return -EINVAL; - conn->received_cm_service_request = false; - msg = gsm48_create_mm_serv_rej(value); if (!msg) { LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n"); diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c index 34f705cbb..a5184b213 100644 --- a/src/libmsc/osmo_msc.c +++ b/src/libmsc/osmo_msc.c @@ -84,22 +84,6 @@ void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) gsm411_sapi_n_reject(conn); } -void subscr_conn_release_when_unused(struct gsm_subscriber_connection *conn) -{ - if (!conn) - return; - if (!conn->fi) - return; - if (!(conn->fi->state == SUBSCR_CONN_S_ACCEPTED - || conn->fi->state == SUBSCR_CONN_S_COMMUNICATING)) { - DEBUGP(DMM, "%s: %s: conn still being established (%s)\n", - vlr_subscr_name(conn->vsub), __func__, - osmo_fsm_inst_state_name(conn->fi)); - return; - } - osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_RELEASE_WHEN_UNUSED, NULL); -} - /* receive a Level 3 Complete message and return MSC_CONN_ACCEPT or * MSC_CONN_REJECT */ int msc_compl_l3(struct gsm_subscriber_connection *conn, @@ -108,9 +92,6 @@ int msc_compl_l3(struct gsm_subscriber_connection *conn, msc_subscr_conn_get(conn, MSC_CONN_USE_COMPL_L3); gsm0408_dispatch(conn, msg); - subscr_conn_release_when_unused(conn); - - /* If this should be kept, the conn->fi has placed a use_count */ msc_subscr_conn_put(conn, MSC_CONN_USE_COMPL_L3); /* Always return acceptance, because even if the conn was not accepted, @@ -142,7 +123,6 @@ void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct ms msc_subscr_conn_get(conn, MSC_CONN_USE_DTAP); gsm0408_dispatch(conn, msg); - subscr_conn_release_when_unused(conn); msc_subscr_conn_put(conn, MSC_CONN_USE_DTAP); } @@ -232,63 +212,6 @@ void msc_cipher_mode_compl(struct gsm_subscriber_connection *conn, vlr_subscr_rx_ciph_res(conn->vsub, &ciph_res); } -struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network) -{ - struct gsm_subscriber_connection *conn; - - conn = talloc_zero(network, struct gsm_subscriber_connection); - if (!conn) - return NULL; - - conn->network = network; - llist_add_tail(&conn->entry, &network->subscr_conns); - return conn; -} - -void msc_subscr_cleanup(struct vlr_subscr *vsub) -{ - if (!vsub) - return; - vsub->lu_fsm = NULL; -} - -void msc_subscr_con_cleanup(struct gsm_subscriber_connection *conn) -{ - if (!conn) - return; - - if (conn->vsub) { - DEBUGP(DRLL, "subscr %s: Freeing subscriber connection\n", - vlr_subscr_name(conn->vsub)); - msc_subscr_cleanup(conn->vsub); - conn->vsub->msc_conn_ref = NULL; - vlr_subscr_put(conn->vsub); - conn->vsub = NULL; - } else - DEBUGP(DRLL, "Freeing subscriber connection" - " with NULL subscriber\n"); - - if (!conn->fi) - return; - - osmo_fsm_inst_term(conn->fi, - (conn->fi->state == SUBSCR_CONN_S_RELEASED) - ? OSMO_FSM_TERM_REGULAR - : OSMO_FSM_TERM_ERROR, - NULL); -} - -void msc_subscr_con_free(struct gsm_subscriber_connection *conn) -{ - if (!conn) - return; - - msc_subscr_con_cleanup(conn); - - llist_del(&conn->entry); - talloc_free(conn); -} - /* Receive a CLEAR REQUEST from BSC */ int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) { @@ -296,66 +219,6 @@ int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) return 1; } -static void msc_subscr_conn_release_all(struct gsm_subscriber_connection *conn, uint32_t cause) -{ - if (conn->in_release) - return; - conn->in_release = true; - - /* If we're closing in a middle of a trans, we need to clean up */ - trans_conn_closed(conn); - - switch (conn->via_ran) { - case RAN_UTRAN_IU: - ranap_iu_tx_release(conn->iu.ue_ctx, NULL); - /* FIXME: keep the conn until the Iu Release Outcome is - * received from the UE, or a timeout expires. For now, the log - * says "unknown UE" for each release outcome. */ - break; - case RAN_GERAN_A: - a_iface_tx_clear_cmd(conn); - break; - default: - LOGP(DMM, LOGL_ERROR, "%s: Unknown RAN type, cannot tx release/clear\n", - vlr_subscr_name(conn->vsub)); - break; - } -} - -/* If the conn->fi is still present, dispatch SUBSCR_CONN_E_CN_CLOSE - * event to gracefully terminate the connection. If the fi is already - * cleared, call msc_subscr_conn_release_all() to take release actions. - * \param cause a GSM_CAUSE_* constant, e.g. GSM_CAUSE_AUTH_FAILED. - */ -void msc_subscr_conn_close(struct gsm_subscriber_connection *conn, - uint32_t cause) -{ - if (!conn) - return; - if (conn->in_release) { - DEBUGP(DMM, "msc_subscr_conn_close(vsub=%s, cause=%u):" - " already dispatching release, ignore.\n", - vlr_subscr_name(conn->vsub), cause); - return; - } - if (!conn->fi) { - DEBUGP(DMM, "msc_subscr_conn_close(vsub=%s, cause=%u): no conn fsm," - " releasing directly without release event.\n", - vlr_subscr_name(conn->vsub), cause); - /* In case of an IMSI Detach, we don't have fi. Release - * anyway to ensure a timely Iu Release / BSSMAP Clear. */ - msc_subscr_conn_release_all(conn, cause); - return; - } - if (conn->fi->state == SUBSCR_CONN_S_RELEASED) { - DEBUGP(DMM, "msc_subscr_conn_close(vsub=%s, cause=%u):" - " conn fsm already releasing, ignore.\n", - vlr_subscr_name(conn->vsub), cause); - return; - } - osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_CN_CLOSE, &cause); -} - /* increment the ref-count. Needs to be called by every user */ struct gsm_subscriber_connection * _msc_subscr_conn_get(struct gsm_subscriber_connection *conn, @@ -364,12 +227,6 @@ _msc_subscr_conn_get(struct gsm_subscriber_connection *conn, { OSMO_ASSERT(conn); - if (conn->in_release) - LOGPSRC(DREF, LOGL_ERROR, file, line, - "%s: MSC conn use error: using conn that is already in release (%s)\n", - vlr_subscr_name(conn->vsub), - msc_subscr_conn_use_name(balance_token)); - if (balance_token != MSC_CONN_USE_UNTRACKED) { uint32_t flag = 1 << balance_token; OSMO_ASSERT(balance_token < 32); @@ -423,18 +280,20 @@ void _msc_subscr_conn_put(struct gsm_subscriber_connection *conn, conn->use_count, conn->use_tokens); if (conn->use_count == 0) - msc_subscr_con_free(conn); + osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_UNUSED, NULL); } const struct value_string msc_subscr_conn_use_names[] = { {MSC_CONN_USE_UNTRACKED, "UNTRACKED"}, {MSC_CONN_USE_COMPL_L3, "compl_l3"}, {MSC_CONN_USE_DTAP, "dtap"}, - {MSC_CONN_USE_FSM, "fsm"}, + {MSC_CONN_USE_AUTH_CIPH, "auth+ciph"}, + {MSC_CONN_USE_CM_SERVICE, "cm_service"}, {MSC_CONN_USE_TRANS_CC, "trans_cc"}, {MSC_CONN_USE_TRANS_SMS, "trans_sms"}, {MSC_CONN_USE_TRANS_USSD, "trans_ussd"}, {MSC_CONN_USE_SILENT_CALL, "silent_call"}, + {MSC_CONN_USE_RELEASE, "release"}, {0, NULL}, }; diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c index 5629d26f9..e3a6b248c 100644 --- a/src/libmsc/subscr_conn.c +++ b/src/libmsc/subscr_conn.c @@ -33,106 +33,147 @@ #include <osmocom/msc/a_iface.h> #include <osmocom/msc/iucs.h> +#include "../../bscconfig.h" +#ifdef BUILD_IU +#include <osmocom/ranap/iu_client.h> +#else +#include <osmocom/msc/iu_dummy.h> +#endif + #define SUBSCR_CONN_TIMEOUT 5 /* seconds */ static const struct value_string subscr_conn_fsm_event_names[] = { OSMO_VALUE_STRING(SUBSCR_CONN_E_INVALID), - OSMO_VALUE_STRING(SUBSCR_CONN_E_START), + OSMO_VALUE_STRING(SUBSCR_CONN_E_COMPLETE_LAYER_3), OSMO_VALUE_STRING(SUBSCR_CONN_E_ACCEPTED), OSMO_VALUE_STRING(SUBSCR_CONN_E_COMMUNICATING), OSMO_VALUE_STRING(SUBSCR_CONN_E_RELEASE_WHEN_UNUSED), OSMO_VALUE_STRING(SUBSCR_CONN_E_MO_CLOSE), OSMO_VALUE_STRING(SUBSCR_CONN_E_CN_CLOSE), + OSMO_VALUE_STRING(SUBSCR_CONN_E_UNUSED), { 0, NULL } }; -static void paging_event(struct gsm_subscriber_connection *conn, - enum gsm_paging_event pe) +static void update_counters(struct osmo_fsm_inst *fi, bool conn_accepted) { - subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->vsub); + struct gsm_subscriber_connection *conn = fi->priv; + switch (conn->complete_layer3_type) { + case COMPLETE_LAYER3_LU: + rate_ctr_inc(&conn->network->msc_ctrs->ctr[ + conn_accepted ? MSC_CTR_LOC_UPDATE_COMPLETED + : MSC_CTR_LOC_UPDATE_FAILED]); + break; + case COMPLETE_LAYER3_CM_SERVICE_REQ: + rate_ctr_inc(&conn->network->msc_ctrs->ctr[ + conn_accepted ? MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED + : MSC_CTR_CM_SERVICE_REQUEST_REJECTED]); + break; + case COMPLETE_LAYER3_PAGING_RESP: + rate_ctr_inc(&conn->network->msc_ctrs->ctr[ + conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED + : MSC_CTR_PAGING_RESP_REJECTED]); + break; + default: + break; + } } -void subscr_conn_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_accepted) { - OSMO_ASSERT(event == SUBSCR_CONN_E_START); - osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_NEW, - SUBSCR_CONN_TIMEOUT, 0); + struct gsm_subscriber_connection *conn = fi->priv; + + update_counters(fi, conn_accepted); + + /* Trigger transactions that we paged for */ + if (conn->complete_layer3_type == COMPLETE_LAYER3_PAGING_RESP) { + subscr_paging_dispatch(GSM_HOOK_RR_PAGING, + conn_accepted ? GSM_PAGING_SUCCEEDED : GSM_PAGING_EXPIRED, + NULL, conn, conn->vsub); + } + + if (conn->complete_layer3_type == COMPLETE_LAYER3_CM_SERVICE_REQ + && conn_accepted) { + conn->received_cm_service_request = true; + msc_subscr_conn_get(conn, MSC_CONN_USE_CM_SERVICE); + } + + if (conn_accepted) + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, conn->vsub); } -void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static void log_close_event(struct osmo_fsm_inst *fi, uint32_t event, void *data) { - struct gsm_subscriber_connection *conn = fi->priv; - bool success; + if (data) + LOGPFSML(fi, LOGL_DEBUG, "Close event, cause %u\n", *(uint32_t*)data); +} - /* If accepted, transition the state, all other cases mean failure. */ +static void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ switch (event) { + case SUBSCR_CONN_E_COMPLETE_LAYER_3: + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_AUTH_CIPH, SUBSCR_CONN_TIMEOUT, 0); + return; + case SUBSCR_CONN_E_ACCEPTED: - osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, - SUBSCR_CONN_TIMEOUT, 0); - break; + evaluate_acceptance_outcome(fi, true); + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, SUBSCR_CONN_TIMEOUT, 0); + return; case SUBSCR_CONN_E_MO_CLOSE: case SUBSCR_CONN_E_CN_CLOSE: - if (data) - LOGPFSM(fi, "Close event, cause %u\n", - *(uint32_t*)data); - /* will release further below, see - * 'if (fi->state != SUBSCR_CONN_S_ACCEPTED)' */ - break; + log_close_event(fi, event, data); + evaluate_acceptance_outcome(fi, false); + /* fall through */ + case SUBSCR_CONN_E_UNUSED: + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASING, SUBSCR_CONN_TIMEOUT, 0); + return; default: - LOGPFSML(fi, LOGL_ERROR, - "Unexpected event: %d %s\n", event, - osmo_fsm_event_name(fi->fsm, event)); - break; + OSMO_ASSERT(false); } +} + +void subscr_conn_fsm_auth_ciph(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + /* If accepted, transition the state, all other cases mean failure. */ + switch (event) { + case SUBSCR_CONN_E_ACCEPTED: + evaluate_acceptance_outcome(fi, true); + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, SUBSCR_CONN_TIMEOUT, 0); + return; - success = (fi->state == SUBSCR_CONN_S_ACCEPTED); + case SUBSCR_CONN_E_UNUSED: + LOGPFSML(fi, LOGL_DEBUG, "Awaiting results for Auth+Ciph, overruling event %s\n", + osmo_fsm_event_name(fi->fsm, event)); + return; - if (conn->complete_layer3_type == COMPLETE_LAYER3_LU) - rate_ctr_inc(&conn->network->msc_ctrs->ctr[ - success ? MSC_CTR_LOC_UPDATE_COMPLETED - : MSC_CTR_LOC_UPDATE_FAILED]); - - /* signal paging success or failure in case this was a paging */ - if (conn->complete_layer3_type == COMPLETE_LAYER3_PAGING_RESP) - paging_event(conn, - success ? GSM_PAGING_SUCCEEDED - : GSM_PAGING_EXPIRED); - - /* FIXME rate counters */ - /*rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]);*/ - - /* On failure, discard the conn */ - if (!success) { - /* TODO: on MO_CLOSE or CN_CLOSE, first go to RELEASING and - * await BSC/RNC confirmation? */ - osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); + case SUBSCR_CONN_E_MO_CLOSE: + case SUBSCR_CONN_E_CN_CLOSE: + log_close_event(fi, event, data); + evaluate_acceptance_outcome(fi, false); + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASING, SUBSCR_CONN_TIMEOUT, 0); return; - } - if (conn->complete_layer3_type == COMPLETE_LAYER3_CM_SERVICE_REQ) { - conn->received_cm_service_request = true; - LOGPFSML(fi, LOGL_DEBUG, "received_cm_service_request = true\n"); - } - osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_RELEASE_WHEN_UNUSED, data); + default: + OSMO_ASSERT(false); + } } -static void subscr_conn_fsm_release_when_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static bool subscr_conn_fsm_has_active_transactions(struct osmo_fsm_inst *fi) { struct gsm_subscriber_connection *conn = fi->priv; struct gsm_trans *trans; if (conn->silent_call) { LOGPFSML(fi, LOGL_DEBUG, "%s: silent call still active\n", __func__); - return; + return true; } if (conn->received_cm_service_request) { LOGPFSML(fi, LOGL_DEBUG, "%s: still awaiting first request after a CM Service Request\n", __func__); - return; + return true; } if (conn->vsub && !llist_empty(&conn->vsub->cs.requests)) { @@ -143,24 +184,23 @@ static void subscr_conn_fsm_release_when_unused(struct osmo_fsm_inst *fi, uint32 __func__, sr->label); } } - return; + return true; } if ((trans = trans_has_conn(conn))) { LOGPFSML(fi, LOGL_DEBUG, "%s: connection still has active transaction: %s\n", __func__, gsm48_pdisc_name(trans->protocol)); - return; + return true; } - LOGPFSML(fi, LOGL_DEBUG, "%s: releasing conn\n", __func__); - osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); + return false; } static void subscr_conn_fsm_accepted_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { - struct gsm_subscriber_connection *conn = fi->priv; - osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, conn->vsub); + if (!subscr_conn_fsm_has_active_transactions(fi)) + osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_UNUSED, NULL); } static void subscr_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -170,17 +210,17 @@ static void subscr_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, v osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_COMMUNICATING, 0, 0); return; - case SUBSCR_CONN_E_RELEASE_WHEN_UNUSED: - subscr_conn_fsm_release_when_unused(fi, event, data); + case SUBSCR_CONN_E_MO_CLOSE: + case SUBSCR_CONN_E_CN_CLOSE: + log_close_event(fi, event, data); + /* fall through */ + case SUBSCR_CONN_E_UNUSED: + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASING, SUBSCR_CONN_TIMEOUT, 0); return; default: - break; + OSMO_ASSERT(false); } - /* Whatever unexpected happens in the accepted state, it means release. - * Even if an unexpected event is passed, the safest thing to do is - * discard the conn. We don't expect another SUBSCR_CONN_E_ACCEPTED. */ - osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); } static void subscr_conn_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -190,64 +230,107 @@ static void subscr_conn_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t eve /* no-op */ return; - case SUBSCR_CONN_E_RELEASE_WHEN_UNUSED: - subscr_conn_fsm_release_when_unused(fi, event, data); + case SUBSCR_CONN_E_MO_CLOSE: + case SUBSCR_CONN_E_CN_CLOSE: + log_close_event(fi, event, data); + /* fall through */ + case SUBSCR_CONN_E_UNUSED: + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASING, SUBSCR_CONN_TIMEOUT, 0); return; default: - break; + OSMO_ASSERT(false); } - /* Whatever unexpected happens in the accepted state, it means release. - * Even if an unexpected event is passed, the safest thing to do is - * discard the conn. We don't expect another SUBSCR_CONN_E_ACCEPTED. */ - osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); } -static void subscr_conn_fsm_cleanup(struct osmo_fsm_inst *fi, - enum osmo_fsm_term_cause cause) +static int subscr_conn_fsm_timeout(struct osmo_fsm_inst *fi) { struct gsm_subscriber_connection *conn = fi->priv; - fi->priv = NULL; - - if (!conn) - return; - conn->fi = NULL; - msc_subscr_conn_close(conn, cause); - msc_subscr_conn_put(conn, MSC_CONN_USE_FSM); + if (msc_subscr_conn_in_release(conn)) { + LOGPFSML(fi, LOGL_ERROR, "Timeout while releasing, discarding right now\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, NULL); + } else { + uint32_t cause = GSM_CAUSE_NET_FAIL; + osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_CN_CLOSE, &cause); + } + return 0; } -int subscr_conn_fsm_timeout(struct osmo_fsm_inst *fi) +static void subscr_conn_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gsm_subscriber_connection *conn = fi->priv; - if (conn) - vlr_subscr_conn_timeout(conn->vsub); - else - osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_CN_CLOSE, NULL); - return 0; + + /* While we're still checking on release, prevent a last use count decrement from deallocating */ + msc_subscr_conn_get(conn, MSC_CONN_USE_RELEASE); + + /* Cancel pending CM Service Requests */ + if (conn->received_cm_service_request) { + conn->received_cm_service_request = false; + msc_subscr_conn_put(conn, MSC_CONN_USE_CM_SERVICE); + } + + /* Cancel all VLR FSMs, if any */ + vlr_subscr_conn_timeout(conn->vsub); + + /* If we're closing in a middle of a trans, we need to clean up */ + trans_conn_closed(conn); + + switch (conn->via_ran) { + case RAN_GERAN_A: + a_iface_tx_clear_cmd(conn); + break; + case RAN_UTRAN_IU: + ranap_iu_tx_release(conn->iu.ue_ctx, NULL); + break; + default: + LOGP(DMM, LOGL_ERROR, "%s: Unknown RAN type, cannot tx release/clear\n", + vlr_subscr_name(conn->vsub)); + break; + } + + /* FIXME: keep the conn until the Iu Release Outcome is + * received from the UE, or a timeout expires. For now, the log + * says "unknown UE" for each release outcome. */ + msc_subscr_conn_put(conn, MSC_CONN_USE_RELEASE); +} + +static void subscr_conn_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + OSMO_ASSERT(event == SUBSCR_CONN_E_UNUSED); + osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); } -static void subscr_conn_fsm_release(struct osmo_fsm_inst *fi, uint32_t prev_state) +static void subscr_conn_fsm_released(struct osmo_fsm_inst *fi, uint32_t prev_state) { + /* Terminate, deallocate and also deallocate the gsm_subscriber_connection, which is allocated as + * a talloc child of fi. Also calls the cleanup function. */ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } #define S(x) (1 << (x)) static const struct osmo_fsm_state subscr_conn_fsm_states[] = { - [SUBSCR_CONN_S_INIT] = { - .name = OSMO_STRINGIFY(SUBSCR_CONN_S_INIT), - .in_event_mask = S(SUBSCR_CONN_E_START), - .out_state_mask = S(SUBSCR_CONN_S_NEW), - .action = subscr_conn_fsm_init, - }, [SUBSCR_CONN_S_NEW] = { .name = OSMO_STRINGIFY(SUBSCR_CONN_S_NEW), + .in_event_mask = S(SUBSCR_CONN_E_COMPLETE_LAYER_3) | + S(SUBSCR_CONN_E_ACCEPTED) | + S(SUBSCR_CONN_E_MO_CLOSE) | + S(SUBSCR_CONN_E_CN_CLOSE) | + S(SUBSCR_CONN_E_UNUSED), + .out_state_mask = S(SUBSCR_CONN_S_AUTH_CIPH) | + S(SUBSCR_CONN_S_ACCEPTED) | + S(SUBSCR_CONN_S_RELEASING), + .action = subscr_conn_fsm_new, + }, + [SUBSCR_CONN_S_AUTH_CIPH] = { + .name = OSMO_STRINGIFY(SUBSCR_CONN_S_AUTH_CIPH), .in_event_mask = S(SUBSCR_CONN_E_ACCEPTED) | S(SUBSCR_CONN_E_MO_CLOSE) | - S(SUBSCR_CONN_E_CN_CLOSE), + S(SUBSCR_CONN_E_CN_CLOSE) | + S(SUBSCR_CONN_E_UNUSED), .out_state_mask = S(SUBSCR_CONN_S_ACCEPTED) | - S(SUBSCR_CONN_S_RELEASED), - .action = subscr_conn_fsm_new, + S(SUBSCR_CONN_S_RELEASING), + .action = subscr_conn_fsm_auth_ciph, }, [SUBSCR_CONN_S_ACCEPTED] = { .name = OSMO_STRINGIFY(SUBSCR_CONN_S_ACCEPTED), @@ -256,8 +339,9 @@ static const struct osmo_fsm_state subscr_conn_fsm_states[] = { S(SUBSCR_CONN_E_RELEASE_WHEN_UNUSED) | S(SUBSCR_CONN_E_ACCEPTED) | S(SUBSCR_CONN_E_MO_CLOSE) | - S(SUBSCR_CONN_E_CN_CLOSE), - .out_state_mask = S(SUBSCR_CONN_S_RELEASED) | + S(SUBSCR_CONN_E_CN_CLOSE) | + S(SUBSCR_CONN_E_UNUSED), + .out_state_mask = S(SUBSCR_CONN_S_RELEASING) | S(SUBSCR_CONN_S_COMMUNICATING), .onenter = subscr_conn_fsm_accepted_enter, .action = subscr_conn_fsm_accepted, @@ -269,16 +353,26 @@ static const struct osmo_fsm_state subscr_conn_fsm_states[] = { S(SUBSCR_CONN_E_ACCEPTED) | S(SUBSCR_CONN_E_COMMUNICATING) | S(SUBSCR_CONN_E_MO_CLOSE) | - S(SUBSCR_CONN_E_CN_CLOSE), - .out_state_mask = S(SUBSCR_CONN_S_RELEASED), + S(SUBSCR_CONN_E_CN_CLOSE) | + S(SUBSCR_CONN_E_UNUSED), + .out_state_mask = S(SUBSCR_CONN_S_RELEASING), .action = subscr_conn_fsm_communicating, }, + [SUBSCR_CONN_S_RELEASING] = { + .name = OSMO_STRINGIFY(SUBSCR_CONN_S_RELEASING), + .in_event_mask = S(SUBSCR_CONN_E_UNUSED), + .out_state_mask = S(SUBSCR_CONN_S_RELEASED), + .onenter = subscr_conn_fsm_releasing_onenter, + .action = subscr_conn_fsm_releasing, + }, [SUBSCR_CONN_S_RELEASED] = { .name = OSMO_STRINGIFY(SUBSCR_CONN_S_RELEASED), - .onenter = subscr_conn_fsm_release, + .onenter = subscr_conn_fsm_released, }, }; +static void subscr_conn_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause); + static struct osmo_fsm subscr_conn_fsm = { .name = "Subscr_Conn", .states = subscr_conn_fsm_states, @@ -310,34 +404,79 @@ char *msc_subscr_conn_get_conn_id(struct gsm_subscriber_connection *conn) return id; } -int msc_create_conn_fsm(struct gsm_subscriber_connection *conn, const char *id) +/* Tidy up before the FSM deallocates */ +static void subscr_conn_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { - struct osmo_fsm_inst *fi; - OSMO_ASSERT(conn); + struct gsm_subscriber_connection *conn = fi->priv; + + if (subscr_conn_fsm_has_active_transactions(fi)) + LOGPFSML(fi, LOGL_ERROR, "Deallocating despite active transactions\n"); - if (conn->fi) { - LOGP(DMM, LOGL_ERROR, - "%s: Error: connection already in use\n", id); - return -EINVAL; + if (!conn) { + LOGP(DRLL, LOGL_ERROR, "Freeing NULL subscriber connection\n"); + return; } - /* Allocate the FSM not with the subscr_conn. Semantically it would - * make sense, but in subscr_conn_fsm_cleanup(), we want to discard the - * subscriber connection. If the FSM is freed along with the subscriber - * connection, then in _osmo_fsm_inst_term() the osmo_fsm_inst_free() - * that follows the cleanup() call would run into a double free. */ - fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, conn->network, - msc_subscr_conn_get(conn, MSC_CONN_USE_FSM), - LOGL_DEBUG, id); + if (conn->vsub) { + DEBUGP(DRLL, "%s: Freeing subscriber connection\n", vlr_subscr_name(conn->vsub)); + conn->vsub->lu_fsm = NULL; + conn->vsub->msc_conn_ref = NULL; + vlr_subscr_put(conn->vsub); + conn->vsub = NULL; + } else + DEBUGP(DRLL, "Freeing subscriber connection with NULL subscriber\n"); - if (!fi) { - LOGP(DMM, LOGL_ERROR, - "%s: Failed to allocate subscr conn master FSM\n", id); - return -ENOMEM; + llist_del(&conn->entry); +} + +/* Signal success of Complete Layer 3. Allow to keep the conn open for Auth and Ciph. */ +void msc_subscr_conn_complete_layer_3(struct gsm_subscriber_connection *conn) +{ + if (!conn) + return; + osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_COMPLETE_LAYER_3, NULL); +} + +void subscr_conn_release_when_unused(struct gsm_subscriber_connection *conn) +{ + if (!conn) + return; + if (msc_subscr_conn_in_release(conn)) { + DEBUGP(DMM, "%s: %s: conn already in release (%s)\n", + vlr_subscr_name(conn->vsub), __func__, + osmo_fsm_inst_state_name(conn->fi)); + return; } - conn->fi = fi; - osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_START, NULL); - return 0; + if (conn->fi->state == SUBSCR_CONN_S_NEW) { + DEBUGP(DMM, "%s: %s: conn still being established (%s)\n", + vlr_subscr_name(conn->vsub), __func__, + osmo_fsm_inst_state_name(conn->fi)); + return; + } + osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_RELEASE_WHEN_UNUSED, NULL); +} + +void msc_subscr_conn_close(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + if (!conn) { + LOGP(DMM, LOGL_ERROR, "Cannot release NULL connection\n"); + return; + } + if (msc_subscr_conn_in_release(conn)) { + DEBUGP(DMM, "%s(vsub=%s, cause=%u): already in release, ignore.\n", + __func__, vlr_subscr_name(conn->vsub), cause); + return; + } + osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_CN_CLOSE, &cause); +} + +bool msc_subscr_conn_in_release(struct gsm_subscriber_connection *conn) +{ + if (conn->fi->state == SUBSCR_CONN_S_RELEASING) + return true; + if (conn->fi->state == SUBSCR_CONN_S_RELEASED) + return true; + return false; } bool msc_subscr_conn_is_accepted(const struct gsm_subscriber_connection *conn) @@ -346,22 +485,16 @@ bool msc_subscr_conn_is_accepted(const struct gsm_subscriber_connection *conn) return false; if (!conn->vsub) return false; - if (!conn->fi) - return false; if (!(conn->fi->state == SUBSCR_CONN_S_ACCEPTED || conn->fi->state == SUBSCR_CONN_S_COMMUNICATING)) return false; return true; } +/* Indicate that *some* communication is happening with the phone. */ void msc_subscr_conn_communicating(struct gsm_subscriber_connection *conn) { - OSMO_ASSERT(conn); - /* This function is called to indicate that *some* communication is happening with the phone. - * Late in the process, that may be a Release Confirm and the FSM and conn are already in - * teardown. No need to signal SUBSCR_CONN_E_COMMUNICATING then. */ - if (conn->fi) - osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_COMMUNICATING, NULL); + osmo_fsm_inst_dispatch(conn->fi, SUBSCR_CONN_E_COMMUNICATING, NULL); } void msc_subscr_conn_init(void) @@ -369,26 +502,48 @@ void msc_subscr_conn_init(void) osmo_fsm_register(&subscr_conn_fsm); } -/* Allocate a new subscriber conn. */ +/* Allocate a new subscriber conn and FSM. + * Deallocation is by msc_subscr_conn_put(): when the use count reaches zero, the + * SUBSCR_CONN_E_RELEASE_COMPLETE event is dispatched, the FSM terminates and deallocates both FSM and + * conn. As long as the FSM is waiting for responses from the subscriber, it will itself hold a use count + * on the conn. */ struct gsm_subscriber_connection *msc_subscr_conn_alloc(struct gsm_network *network, enum ran_type via_ran, uint16_t lac) { struct gsm_subscriber_connection *conn; + struct osmo_fsm_inst *fi; - conn = talloc_zero(network, struct gsm_subscriber_connection); - if (!conn) + fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, network, NULL, LOGL_DEBUG, NULL); + if (!fi) { + LOGP(DMM, LOGL_ERROR, "Failed to allocate conn FSM\n"); return NULL; + } + + conn = talloc_zero(fi, struct gsm_subscriber_connection); + if (!conn) { + osmo_fsm_inst_free(fi); + return NULL; + } *conn = (struct gsm_subscriber_connection){ .network = network, .via_ran = via_ran, .lac = lac, + .fi = fi, }; + fi->priv = conn; llist_add_tail(&conn->entry, &network->subscr_conns); return conn; } +bool msc_subscr_conn_is_establishing_auth_ciph(const struct gsm_subscriber_connection *conn) +{ + if (!conn) + return false; + return conn->fi->state == SUBSCR_CONN_S_AUTH_CIPH; +} + const struct value_string complete_layer3_type_names[] = { { COMPLETE_LAYER3_NONE, "NONE" }, { COMPLETE_LAYER3_LU, "LU" }, diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c index 147099ed7..28da9f37e 100644 --- a/src/libmsc/transaction.c +++ b/src/libmsc/transaction.c @@ -140,17 +140,13 @@ void trans_free(struct gsm_trans *trans) trans->vsub = NULL; } - llist_del(&trans->entry); - - if (trans->conn) - msc_subscr_conn_put(trans->conn, conn_usage_token); - conn = trans->conn; trans->conn = NULL; + llist_del(&trans->entry); talloc_free(trans); - /* Possibly this was the last transaction used by this conn. */ - subscr_conn_release_when_unused(conn); + if (conn) + msc_subscr_conn_put(conn, conn_usage_token); } /*! allocate an unused transaction ID for the given subscriber |