aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc/osmo_msc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libmsc/osmo_msc.c')
-rw-r--r--src/libmsc/osmo_msc.c316
1 files changed, 267 insertions, 49 deletions
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
index 2389980d..95e58182 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);
+ }
}