aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2021-07-27 03:46:49 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2021-07-29 22:40:59 +0200
commitae98b97382285420ba81549bc874b9fea5e7daa9 (patch)
tree9c4c0799a8518d343ac699e85be109850a25c7fd
parent5bdba0d48de359cc5e633fbfaa1c0ed904a576cc (diff)
implement CM Re-Establish for voice calls
-rw-r--r--include/osmocom/msc/gsm_data.h4
-rw-r--r--include/osmocom/msc/msc_common.h1
-rw-r--r--include/osmocom/msc/vlr.h1
-rw-r--r--src/libmsc/gsm_04_08.c117
-rw-r--r--src/libmsc/msc_a.c15
5 files changed, 136 insertions, 2 deletions
diff --git a/include/osmocom/msc/gsm_data.h b/include/osmocom/msc/gsm_data.h
index 1870804d0..170d88455 100644
--- a/include/osmocom/msc/gsm_data.h
+++ b/include/osmocom/msc/gsm_data.h
@@ -44,6 +44,8 @@ enum {
MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED,
MSC_CTR_PAGING_RESP_REJECTED,
MSC_CTR_PAGING_RESP_ACCEPTED,
+ MSC_CTR_CM_RE_ESTABLISH_REQ_REJECTED,
+ MSC_CTR_CM_RE_ESTABLISH_REQ_ACCEPTED,
MSC_CTR_SMS_SUBMITTED,
MSC_CTR_SMS_NO_RECEIVER,
MSC_CTR_SMS_DELIVERED,
@@ -76,6 +78,8 @@ static const struct rate_ctr_desc msc_ctr_description[] = {
[MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED] = {"cm_service_request:accepted", "Accepted CM Service Requests."},
[MSC_CTR_PAGING_RESP_REJECTED] = {"paging_resp:rejected", "Rejected Paging Responses."},
[MSC_CTR_PAGING_RESP_ACCEPTED] = {"paging_resp:accepted", "Accepted Paging Responses."},
+ [MSC_CTR_CM_RE_ESTABLISH_REQ_REJECTED] = {"cm_re_establish_request:rejected", "Rejected CM Re-Establishing Requests."},
+ [MSC_CTR_CM_RE_ESTABLISH_REQ_ACCEPTED] = {"cm_re_establish_request:accepted", "Accepted CM Re-Establishing Requests."},
[MSC_CTR_SMS_SUBMITTED] = {"sms:submitted", "Total MO SMS received from the MS."},
[MSC_CTR_SMS_NO_RECEIVER] = {"sms:no_receiver", "Failed MO SMS delivery attempts (no receiver found)."},
[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR] = {"sms:deliver_unknown_error", "Failed MO SMS delivery attempts (other reason)."},
diff --git a/include/osmocom/msc/msc_common.h b/include/osmocom/msc/msc_common.h
index e7ac559a0..f3fb0e07a 100644
--- a/include/osmocom/msc/msc_common.h
+++ b/include/osmocom/msc/msc_common.h
@@ -41,6 +41,7 @@ enum complete_layer3_type {
COMPLETE_LAYER3_LU,
COMPLETE_LAYER3_CM_SERVICE_REQ,
COMPLETE_LAYER3_PAGING_RESP,
+ COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ,
};
extern const struct value_string complete_layer3_type_names[];
diff --git a/include/osmocom/msc/vlr.h b/include/osmocom/msc/vlr.h
index 327333333..f12e90675 100644
--- a/include/osmocom/msc/vlr.h
+++ b/include/osmocom/msc/vlr.h
@@ -444,6 +444,7 @@ enum vlr_parq_type {
VLR_PR_ARQ_T_INVALID = 0, /* to guard against unset vars */
VLR_PR_ARQ_T_CM_SERV_REQ,
VLR_PR_ARQ_T_PAGING_RESP,
+ VLR_PR_ARQ_T_CM_RE_ESTABLISH_REQ,
/* FIXME: differentiate between services of 24.008 10.5.3.3 */
};
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index fb450e69a..565e7adb7 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -48,11 +48,13 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/core/fsm.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_roles.h>
+#include <osmocom/msc/call_leg.h>
#include <assert.h>
@@ -824,17 +826,128 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg)
/* Receive a CM Re-establish Request */
static int gsm48_rx_cm_reest_req(struct msc_a *msc_a, struct msgb *msg)
{
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct gsm48_hdr *gh;
+ struct gsm48_service_request *req;
+ struct gsm48_classmark2 *cm2;
+ uint8_t *cm2_buf, cm2_len;
+ bool is_utran;
+ struct vlr_subscr *vsub;
struct osmo_mobile_identity mi;
+ struct msub *prev_msub;
+ struct msc_a *prev_msc_a;
+
int rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false);
if (rc) {
LOGP(DMM, LOGL_ERROR, "CM RE-ESTABLISH REQUEST: cannot decode Mobile Identity\n");
return -EINVAL;
}
+ msc_a->complete_layer3_type = COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ;
+ msub_update_id_from_mi(msc_a->c.msub, &mi);
+
DEBUGP(DMM, "<- CM RE-ESTABLISH REQUEST %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
- /* we don't support CM call re-establishment */
- return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED);
+ gh = (struct gsm48_hdr *) msgb_l3(msg);
+ req = (struct gsm48_service_request *) gh->data;
+
+ /* Unfortunately in Phase1 the Classmark2 length is variable, so we cannot
+ * just use gsm48_service_request struct, and need to parse it manually. */
+ cm2_len = gh->data[1];
+ cm2_buf = gh->data + 2;
+ cm2 = (struct gsm48_classmark2 *) cm2_buf;
+
+ /* Prevent buffer overrun: check the length of Classmark2 */
+ if (cm2_buf + cm2_len > msg->tail) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Rx CM SERVICE REQUEST: Classmark2 "
+ "length=%u is too big\n", cm2_len);
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE);
+ }
+
+ /* Look up the other, previously active connection for this subscriber */
+ vsub = vlr_subscr_find_by_mi(net->vlr, &mi, __func__);
+ prev_msub = msub_for_vsub(vsub);
+ prev_msc_a = msub_msc_a(prev_msub);
+ if (!vsub || !prev_msub || !prev_msc_a) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "CM Re-Establish Request for unknown subscriber: %s\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
+ if (vsub)
+ vlr_subscr_put(vsub, __func__);
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED);
+ }
+
+ LOG_MSC_A(msc_a, LOGL_NOTICE, "New conn requesting Re-Establishment\n");
+ LOG_MSC_A(prev_msc_a, LOGL_NOTICE, "Old conn matching Re-Establishment request (%s)\n",
+ osmo_use_count_to_str_c(OTC_SELECT, &prev_msc_a->use_count));
+
+ if (!prev_msc_a->cc.call_leg || !prev_msc_a->cc.active_trans) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "CM Re-Establish Request only supported for voice calls\n");
+ if (vsub)
+ vlr_subscr_put(vsub, __func__);
+ return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED);
+ }
+
+ msc_a_get(prev_msc_a, __func__);
+
+ /* Move the call_leg and active CC trans over to the new msc_a */
+ call_leg_reparent(prev_msc_a->cc.call_leg,
+ msc_a->c.fi,
+ MSC_EV_CALL_LEG_TERM,
+ MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE,
+ MSC_EV_CALL_LEG_RTP_COMPLETE);
+ msc_a->cc.call_leg = prev_msc_a->cc.call_leg;
+ prev_msc_a->cc.call_leg = NULL;
+
+ msc_a->cc.active_trans = prev_msc_a->cc.active_trans;
+ msc_a->cc.active_trans->msc_a = msc_a;
+ msc_a_get(msc_a, MSC_A_USE_CC);
+ prev_msc_a->cc.active_trans = NULL;
+ msc_a_put(prev_msc_a, MSC_A_USE_CC);
+
+ /* Dis-associate the VLR subscriber from the previous msc_a, so that we can start a new Process Access Request
+ * on the new msc_a. */
+ if (vsub->proc_arq_fsm) {
+ osmo_fsm_inst_term(vsub->proc_arq_fsm, OSMO_FSM_TERM_REGULAR, NULL);
+ vsub->proc_arq_fsm = NULL;
+ }
+ if (prev_msub->vsub) {
+ vlr_subscr_put(prev_msub->vsub, VSUB_USE_MSUB);
+ prev_msub->vsub = NULL;
+ }
+
+ /* Clear the previous conn.
+ * FIXME: we are clearing the previous conn before having authenticated the new conn. That means anyone can send
+ * CM Re-Establishing requests with arbitrary mobile identities without having to authenticate, and can freely
+ * Clear any connections at will. */
+ msc_a_release_cn(prev_msc_a);
+ msc_a_put(prev_msc_a, __func__);
+ prev_msc_a = NULL;
+
+ /* Kick off Authentication and Ciphering for the new conn. */
+ is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU);
+ vlr_proc_acc_req(msc_a->c.fi,
+ MSC_A_EV_AUTHENTICATED, MSC_A_EV_CN_CLOSE, NULL,
+ net->vlr, msc_a,
+ VLR_PR_ARQ_T_CM_RE_ESTABLISH_REQ, 0,
+ &mi, &msc_a->via_cell.lai,
+ is_utran || net->authentication_required,
+ is_utran ? net->uea_encryption : net->a5_encryption_mask > 0x01,
+ req->cipher_key_seq,
+ osmo_gsm48_classmark2_is_r99(cm2, cm2_len),
+ is_utran);
+ vlr_subscr_put(vsub, __func__);
+
+ /* From vlr_proc_acc_req() we expect an implicit dispatch of PR_ARQ_E_START, and we expect
+ * msc_vlr_subscr_assoc() to already have been called and completed. Has an error occurred? */
+ vsub = msc_a_vsub(msc_a);
+ if (!vsub) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "subscriber not allowed to do a CM Service Request\n");
+ return -EIO;
+ }
+
+ vsub->classmark.classmark2 = *cm2;
+ vsub->classmark.classmark2_len = cm2_len;
+ return 0;
}
static int gsm48_rx_mm_imsi_detach_ind(struct msc_a *msc_a, struct msgb *msg)
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
index 0bc7b2697..fa8e8428b 100644
--- a/src/libmsc/msc_a.c
+++ b/src/libmsc/msc_a.c
@@ -120,6 +120,11 @@ static void update_counters(struct osmo_fsm_inst *fi, bool conn_accepted)
case COMPLETE_LAYER3_PAGING_RESP:
rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED : MSC_CTR_PAGING_RESP_REJECTED));
break;
+ case COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ:
+ rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs,
+ conn_accepted ? MSC_CTR_CM_RE_ESTABLISH_REQ_ACCEPTED
+ : MSC_CTR_CM_RE_ESTABLISH_REQ_REJECTED));
+ break;
default:
break;
}
@@ -151,6 +156,15 @@ static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_acce
if (msc_a->complete_layer3_type == COMPLETE_LAYER3_LU)
msc_a_put(msc_a, MSC_A_USE_LOCATION_UPDATING);
+
+ if (msc_a->complete_layer3_type == COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ) {
+ /* Trigger new Assignment to recommence the voice call. A little dance here because normally we verify
+ * that no CC trans is already active. */
+ struct gsm_trans *cc_trans = msc_a->cc.active_trans;
+ msc_a->cc.active_trans = NULL;
+ osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, cc_trans);
+ msc_a_try_call_assignment(cc_trans);
+ }
}
bool msc_a_is_accepted(const struct msc_a *msc_a)
@@ -1092,6 +1106,7 @@ const struct value_string complete_layer3_type_names[] = {
{ COMPLETE_LAYER3_LU, "LU" },
{ COMPLETE_LAYER3_CM_SERVICE_REQ, "CM_SERVICE_REQ" },
{ COMPLETE_LAYER3_PAGING_RESP, "PAGING_RESP" },
+ { COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ, "CM_RE_ESTABLISH_REQ" },
{ 0, NULL }
};