aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc/osmo_msc.c
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2016-06-19 18:06:02 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2017-08-27 17:40:52 +0200
commit9066b74c1fe5439c79e82cff9ab0fdb503a33332 (patch)
tree68489deee447c38f0d3efcfdfa715ec041b0be3f /src/libmsc/osmo_msc.c
parent9ccd6621fb236377c0b71d334f64bede02ec32e7 (diff)
Use libvlr in libmsc (large refactoring)
Original libvlr code is by Harald Welte <laforge@gnumonks.org>, polished and tweaked by Neels Hofmeyr <nhofmeyr@sysmocom.de>. This is a long series of trial-and-error development collapsed in one patch. This may be split in smaller commits if reviewers prefer that. If we can keep it as one, we have saved ourselves the additional separation work. SMS: The SQL based lookup of SMS for attached subscribers no longer works since the SQL database no longer has the subscriber data. Replace with a round-robin on the SMS recipient MSISDNs paired with a VLR subscriber RAM lookup whether the subscriber is currently attached. If there are many SMS for not-attached subscribers in the SMS database, this will become inefficient: a DB hit returns a pending SMS, the RAM lookup will reveal that the subscriber is not attached, after which the DB is hit for the next SMS. It would become more efficient e.g. by having an MSISDN based hash list for the VLR subscribers and by marking non-attached SMS recipients in the SMS database so that they can be excluded with the SQL query already. There is a sanity limit to do at most 100 db hits per attempt to find a pending SMS. So if there are more than 100 stored SMS waiting for their recipients to actually attach to the MSC, it may take more than one SMS queue trigger to deliver SMS for subscribers that are actually attached. This is not very beautiful, but is merely intended to carry us over to a time when we have a proper separate SMSC entity. Introduce gsm_subscriber_connection ref-counting in libmsc. Remove/Disable VTY and CTRL commands to create subscribers, which is now a task of the OsmoHLR. Adjust the python tests accordingly. Remove VTY cmd subscriber-keep-in-ram. Use OSMO_GSUP_PORT = 4222 instead of 2222. See I4222e21686c823985be8ff1f16b1182be8ad6175. So far use the LAC from conn->bts, will be replaced by conn->lac in Id3705236350d5f69e447046b0a764bbabc3d493c. Related: OS#1592 OS#1974 Change-Id: I639544a6cdda77a3aafc4e3446a55393f60e4050
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);
+ }
}