diff options
Diffstat (limited to 'src/libmsc/osmo_msc.c')
-rw-r--r-- | src/libmsc/osmo_msc.c | 316 |
1 files changed, 267 insertions, 49 deletions
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c index 2389980d3..95e58182c 100644 --- a/src/libmsc/osmo_msc.c +++ b/src/libmsc/osmo_msc.c @@ -25,9 +25,12 @@ #include <openbsc/debug.h> #include <openbsc/transaction.h> #include <openbsc/db.h> +#include <openbsc/vlr.h> +#include <openbsc/osmo_msc.h> #include <openbsc/gsm_04_11.h> +/* Receive a SAPI-N-REJECT from BSC */ static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) { int sapi = dlci & 0x7; @@ -36,18 +39,66 @@ static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) gsm411_sapi_n_reject(conn); } -static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +static bool keep_conn(struct gsm_subscriber_connection *conn) { - gsm0408_clear_request(conn, cause); - return 1; + /* TODO: what about a silent call? */ + + if (!conn->conn_fsm) { + DEBUGP(DMM, "No conn_fsm, release conn\n"); + return false; + } + + switch (conn->conn_fsm->state) { + case SUBSCR_CONN_S_NEW: + case SUBSCR_CONN_S_ACCEPTED: + return true; + default: + return false; + } +} + +static void subscr_conn_bump(struct gsm_subscriber_connection *conn) +{ + if (!conn) + return; + if (!conn->conn_fsm) + return; + if (!(conn->conn_fsm->state == SUBSCR_CONN_S_ACCEPTED + || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING)) + return; + osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_BUMP, NULL); } +/* Receive a COMPLETE LAYER3 INFO from BSC */ static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, uint16_t chosen_channel) { - gsm0408_new_conn(conn); + /* Ownership of the gsm_subscriber_connection is still a bit mucky + * between libbsc and libmsc. In libmsc, we use ref counting, but not + * in libbsc. This will become simpler with the MSCSPLIT. */ + + /* reserve for the duration of this function */ + msc_subscr_conn_get(conn); + gsm0408_dispatch(conn, msg); + if (!keep_conn(conn)) { + DEBUGP(DMM, "compl_l3: Discarding conn\n"); + /* keep the use_count reserved, libbsc will discard. If we + * released the ref count and discarded here, libbsc would + * double-free. And we will not change bsc_api semantics. */ + return BSC_API_CONN_POL_REJECT; + } + DEBUGP(DMM, "compl_l3: Keeping conn\n"); + + /* Bump whether the conn wants to be closed */ + subscr_conn_bump(conn); + + /* If this should be kept, the conn->conn_fsm has placed a use_count */ + msc_subscr_conn_put(conn); + return BSC_API_CONN_POL_ACCEPT; + +#if 0 /* * If this is a silent call we want the channel to remain open as long as * possible and this is why we accept this connection regardless of any @@ -55,20 +106,28 @@ static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg */ if (conn->silent_call) return BSC_API_CONN_POL_ACCEPT; - if (conn->loc_operation || conn->sec_operation || conn->anch_operation) + if (conn->sec_operation || conn->anch_operation) return BSC_API_CONN_POL_ACCEPT; if (trans_has_conn(conn)) return BSC_API_CONN_POL_ACCEPT; LOGP(DRR, LOGL_INFO, "MSC Complete L3: Rejecting connection.\n"); return BSC_API_CONN_POL_REJECT; +#endif } +/* Receive a DTAP message from BSC */ static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) { + msc_subscr_conn_get(conn); gsm0408_dispatch(conn, msg); + + /* Bump whether the conn wants to be closed */ + subscr_conn_bump(conn); + msc_subscr_conn_put(conn); } +/* Receive an ASSIGNMENT COMPLETE from BSC */ static void msc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause, uint8_t chosen_channel, uint8_t encr_alg_id, uint8_t speec) @@ -76,58 +135,150 @@ static void msc_assign_compl(struct gsm_subscriber_connection *conn, LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n"); } +/* Receive an ASSIGNMENT FAILURE from BSC */ static void msc_assign_fail(struct gsm_subscriber_connection *conn, uint8_t cause, uint8_t *rr_cause) { LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n"); } +/* Receive a CLASSMARK CHANGE from BSC */ static void msc_classmark_chg(struct gsm_subscriber_connection *conn, const uint8_t *cm2, uint8_t cm2_len, const uint8_t *cm3, uint8_t cm3_len) { - struct gsm_subscriber *subscr = conn->subscr; - - if (subscr) { - subscr->equipment.classmark2_len = cm2_len; - memcpy(subscr->equipment.classmark2, cm2, cm2_len); - if (cm3) { - subscr->equipment.classmark3_len = cm3_len; - memcpy(subscr->equipment.classmark3, cm3, cm3_len); + if (cm2 && cm2_len) { + if (cm2_len > sizeof(conn->classmark.classmark2)) { + LOGP(DRR, LOGL_NOTICE, "%s: classmark2 is %u bytes, truncating at %zu bytes\n", + vlr_subscr_name(conn->vsub), cm2_len, sizeof(conn->classmark.classmark2)); + cm2_len = sizeof(conn->classmark.classmark2); } - db_sync_equipment(&subscr->equipment); + conn->classmark.classmark2_len = cm2_len; + memcpy(conn->classmark.classmark2, cm2, cm2_len); + } + if (cm3 && cm3_len) { + if (cm3_len > sizeof(conn->classmark.classmark3)) { + LOGP(DRR, LOGL_NOTICE, "%s: classmark3 is %u bytes, truncating at %zu bytes\n", + vlr_subscr_name(conn->vsub), cm3_len, sizeof(conn->classmark.classmark3)); + cm3_len = sizeof(conn->classmark.classmark3); + } + conn->classmark.classmark3_len = cm3_len; + memcpy(conn->classmark.classmark3, cm3, cm3_len); } } +/* Receive a CIPHERING MODE COMPLETE from BSC */ static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t alg_id) { - gsm_cbfn *cb; - - DEBUGP(DRR, "CIPHERING MODE COMPLETE\n"); + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + uint8_t mi_type; + char imeisv[GSM48_MI_SIZE] = ""; + struct vlr_ciph_result ciph_res = { .cause = VLR_CIPH_REJECT }; + + if (!gh) { + LOGP(DRR, LOGL_ERROR, "invalid: msgb without l3 header\n"); + return; + } - /* Safety check */ - if (!conn->sec_operation) { - DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n"); + if (!conn) { + LOGP(DRR, LOGL_ERROR, + "invalid: rx Ciphering Mode Complete on NULL conn\n"); + return; + } + if (!conn->vsub) { + LOGP(DRR, LOGL_ERROR, + "invalid: rx Ciphering Mode Complete for NULL subscr\n"); return; } - /* FIXME: check for MI (if any) */ + DEBUGP(DRR, "%s: CIPHERING MODE COMPLETE\n", + vlr_subscr_name(conn->vsub)); - /* Call back whatever was in progress (if anything) ... */ - cb = conn->sec_operation->cb; - if (cb) { - cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED, - NULL, conn, conn->sec_operation->cb_data); + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) { + mi_type = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)[0] & GSM_MI_TYPE_MASK; + if (mi_type == GSM_MI_TYPE_IMEISV + && TLVP_LEN(&tp, GSM48_IE_MOBILE_ID) > 0) { + gsm48_mi_to_string(imeisv, sizeof(imeisv), + TLVP_VAL(&tp, GSM48_IE_MOBILE_ID), + TLVP_LEN(&tp, GSM48_IE_MOBILE_ID)); + ciph_res.imeisv = imeisv; + } } - /* Complete the operation */ - release_security_operation(conn); + ciph_res.cause = VLR_CIPH_COMPL; + 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); + vlr_subscr_put(conn->vsub); + conn->vsub = NULL; + } else + DEBUGP(DRLL, "Freeing subscriber connection" + " with NULL subscriber\n"); + + if (!conn->conn_fsm) + return; + + osmo_fsm_inst_term(conn->conn_fsm, + (conn->conn_fsm->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 */ +static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + msc_subscr_conn_close(conn, cause); + return 1; +} +/* MSC-level operations to be called by libbsc in NITB */ static struct bsc_api msc_handler = { .sapi_n_reject = msc_sapi_n_reject, .compl_l3 = msc_compl_l3, @@ -137,41 +288,108 @@ static struct bsc_api msc_handler = { .assign_fail = msc_assign_fail, .classmark_chg = msc_classmark_chg, .cipher_mode_compl = msc_ciph_m_compl, + .conn_cleanup = msc_subscr_con_cleanup, }; struct bsc_api *msc_bsc_api() { return &msc_handler; } -/* lchan release handling */ -void msc_release_connection(struct gsm_subscriber_connection *conn) +static void msc_subscr_conn_release_all(struct gsm_subscriber_connection *conn, uint32_t cause) { - /* skip when we are in release, e.g. due an error */ 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: + /* future: iu_tx_release(conn->iu.ue_ctx, NULL); */ + break; + case RAN_GERAN_A: + /* future: 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; + } +} - /* skip releasing of silent calls as they have no transaction */ - if (conn->silent_call) +/* If the conn->conn_fsm is still present, dispatch SUBSCR_CONN_E_CN_CLOSE + * event to gracefully terminate the connection. If the conn_fsm 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; - - /* check if there is a pending operation */ - if (conn->loc_operation || conn->sec_operation || conn->anch_operation) + 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 (trans_has_conn(conn)) + } + if (!conn->conn_fsm) { + 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 conn_fsm. Release + * anyway to ensure a timely Iu Release / BSSMAP Clear. */ + msc_subscr_conn_release_all(conn, cause); return; + } + if (conn->conn_fsm->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->conn_fsm, SUBSCR_CONN_E_CN_CLOSE, &cause); +} - /* no more connections, asking to release the channel */ +/* increment the ref-count. Needs to be called by every user */ +struct gsm_subscriber_connection * +_msc_subscr_conn_get(struct gsm_subscriber_connection *conn, + const char *file, int line) +{ + OSMO_ASSERT(conn); - /* - * We had stopped the LU expire timer T3212. Now we are about - * to send the MS back to the idle state and this should lead - * to restarting the timer. Set the new expiration time. - */ - if (conn->expire_timer_stopped) - subscr_update_expire_lu(conn->subscr, conn->bts); + if (conn->in_release) + return NULL; - conn->in_release = 1; - gsm0808_clear(conn); - msc_subscr_con_free(conn); + conn->use_count++; + LOGPSRC(DREF, LOGL_DEBUG, file, line, + "%s: MSC conn use + 1 == %u\n", + vlr_subscr_name(conn->vsub), conn->use_count); + + return conn; +} + +/* decrement the ref-count. Once it reaches zero, we release */ +void _msc_subscr_conn_put(struct gsm_subscriber_connection *conn, + const char *file, int line) +{ + OSMO_ASSERT(conn); + + if (conn->use_count == 0) { + LOGPSRC(DREF, LOGL_ERROR, file, line, + "%s: MSC conn use - 1 failed: is already 0\n", + vlr_subscr_name(conn->vsub)); + return; + } + + conn->use_count--; + LOGPSRC(DREF, LOGL_DEBUG, file, line, + "%s: MSC conn use - 1 == %u\n", + vlr_subscr_name(conn->vsub), conn->use_count); + + if (conn->use_count == 0) { + gsm0808_clear(conn); + bsc_subscr_con_free(conn); + } } |