diff --git a/include/osmocom/msc/ran_msg.h b/include/osmocom/msc/ran_msg.h index 3b08b466c..1303ba3f9 100644 --- a/include/osmocom/msc/ran_msg.h +++ b/include/osmocom/msc/ran_msg.h @@ -220,6 +220,8 @@ struct ran_msg { } cipher_mode_reject; struct { const char *imsi; + bool last_eutran_plmn_present; + struct osmo_plmn_id last_eutran_plmn; } common_id; struct { enum gsm48_reject_value cause; diff --git a/include/osmocom/msc/vlr.h b/include/osmocom/msc/vlr.h index 3b9bbc41c..6e65283d8 100644 --- a/include/osmocom/msc/vlr.h +++ b/include/osmocom/msc/vlr.h @@ -193,6 +193,8 @@ struct vlr_subscr { vlr_sgs_lu_mminfo_cb_t mminfo_cb; enum sgsap_service_ind paging_serv_ind; struct osmo_timer_list Ts5; + bool last_eutran_plmn_present; + struct osmo_plmn_id last_eutran_plmn; } sgs; struct osmo_gsm48_classmark classmark; @@ -398,6 +400,8 @@ void vlr_subscr_set_imsi(struct vlr_subscr *vsub, const char *imsi); void vlr_subscr_set_imei(struct vlr_subscr *vsub, const char *imei); void vlr_subscr_set_imeisv(struct vlr_subscr *vsub, const char *imeisv); void vlr_subscr_set_msisdn(struct vlr_subscr *vsub, const char *msisdn); +void vlr_subscr_set_last_used_eutran_plmn_id(struct vlr_subscr *vsub, + const struct osmo_plmn_id *last_eutran_plmn); bool vlr_subscr_matches_imsi(struct vlr_subscr *vsub, const char *imsi); bool vlr_subscr_matches_tmsi(struct vlr_subscr *vsub, uint32_t tmsi); diff --git a/include/osmocom/msc/vlr_sgs.h b/include/osmocom/msc/vlr_sgs.h index fa9d948a1..1a4984db8 100644 --- a/include/osmocom/msc/vlr_sgs.h +++ b/include/osmocom/msc/vlr_sgs.h @@ -97,7 +97,7 @@ void vlr_sgs_reset(struct vlr_instance *vlr); int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg, vlr_sgs_lu_response_cb_t response_cb, vlr_sgs_lu_paging_cb_t paging_cb, vlr_sgs_lu_mminfo_cb_t mminfo_cb, char *mme_name, enum vlr_lu_type type, const char *imsi, - struct osmo_location_area_id *new_lai); + struct osmo_location_area_id *new_lai, struct osmo_plmn_id *last_eutran_plmn); void vlr_sgs_loc_update_acc_sent(struct vlr_subscr *vsub); void vlr_sgs_loc_update_rej_sent(struct vlr_subscr *vsub); void vlr_sgs_detach(struct vlr_instance *vlr, const char *imsi, bool eps); diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c index 63790598d..58ef074f4 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -1368,12 +1368,19 @@ int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref, enum osmo_cm_service_type cm_serv static int msc_vlr_tx_common_id(void *msc_conn_ref) { struct msc_a *msc_a = msc_conn_ref; + struct vlr_subscr *vsub = msc_a_vsub(msc_a); struct ran_msg msg = { .msg_type = RAN_MSG_COMMON_ID, .common_id = { - .imsi = msc_a_vsub(msc_a)->imsi, + .imsi = vsub->imsi, + .last_eutran_plmn_present = vsub->sgs.last_eutran_plmn_present, }, }; + if (vsub->sgs.last_eutran_plmn_present) { + memcpy(&msg.common_id.last_eutran_plmn, &vsub->sgs.last_eutran_plmn, + sizeof(vsub->sgs.last_eutran_plmn)); + } + return msc_a_ran_down(msc_a, MSC_ROLE_I, &msg); } diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c index 0645c5434..daa5bc7f1 100644 --- a/src/libmsc/msc_a.c +++ b/src/libmsc/msc_a.c @@ -1648,8 +1648,13 @@ int msc_tx_common_id(struct msc_a *msc_a, enum msc_role to_role) .msg_type = RAN_MSG_COMMON_ID, .common_id = { .imsi = vsub->imsi, + .last_eutran_plmn_present = vsub->sgs.last_eutran_plmn_present, }, }; + if (vsub->sgs.last_eutran_plmn_present) { + memcpy(&msg.common_id.last_eutran_plmn, &vsub->sgs.last_eutran_plmn, + sizeof(vsub->sgs.last_eutran_plmn)); + } return msc_a_ran_down(msc_a, to_role, &msg); } diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c index 2890076d0..4cce2899e 100644 --- a/src/libmsc/ran_msg_a.c +++ b/src/libmsc/ran_msg_a.c @@ -1218,7 +1218,11 @@ static struct msgb *_ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct return ran_a_make_assignment_command(caller_fi, &ran_enc_msg->assignment_command); case RAN_MSG_COMMON_ID: - return gsm0808_create_common_id(ran_enc_msg->common_id.imsi, NULL, NULL); + return gsm0808_create_common_id(ran_enc_msg->common_id.imsi, NULL, + ran_enc_msg->common_id.last_eutran_plmn_present ? + &ran_enc_msg->common_id.last_eutran_plmn : + NULL + ); case RAN_MSG_CIPHER_MODE_COMMAND: return ran_a_make_cipher_mode_command(caller_fi, &ran_enc_msg->cipher_mode_command); diff --git a/src/libmsc/sgs_iface.c b/src/libmsc/sgs_iface.c index d13449d29..04302ae4d 100644 --- a/src/libmsc/sgs_iface.c +++ b/src/libmsc/sgs_iface.c @@ -608,12 +608,14 @@ static int sgs_rx_loc_upd_req(struct sgs_connection *sgc, struct msgb *msg, cons char *mme_name; struct vlr_sgs_cfg vlr_sgs_cfg; struct vlr_subscr *vsub; + struct osmo_plmn_id last_eutran_plmn_buf, *last_eutran_plmn = NULL; /* Check for lingering connections */ vsub = vlr_subscr_find_by_imsi(gsm_network->vlr, imsi, __func__); if (vsub) { subscr_conn_toss(vsub); vlr_subscr_put(vsub, __func__); + vsub = NULL; } /* Determine MME-Name */ @@ -639,11 +641,29 @@ static int sgs_rx_loc_upd_req(struct sgs_connection *sgc, struct msgb *msg, cons return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_MISSING_MAND_IE, msg, SGSAP_IE_LAI); gsm48_decode_lai2(gsm48_lai, &new_lai); + /* 3GPP TS 23.272 sec 4.3.3 (CSFB): + * "During the SGs location update procedure, obtaining the last used LTE PLMN ID via TAI" + */ + if (TLVP_PRES_LEN(tp, SGSAP_IE_TAI, 3)) { + last_eutran_plmn = &last_eutran_plmn_buf; + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_TAI), last_eutran_plmn); + /* TODO: we could also gather the TAC from here, but we don't need it yet */ + } else if (TLVP_PRES_LEN(tp, SGSAP_IE_EUTRAN_CGI, 3)) { + /* Since TAI is optional, let's try harder getting Last Used + * E-UTRAN PLMN ID by fetching it from E-UTRAN CGI */ + last_eutran_plmn = &last_eutran_plmn_buf; + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_EUTRAN_CGI), last_eutran_plmn); + /* TODO: we could also gather the ECI from here, but we don't need it yet */ + } else { + LOGSGC(sgc, LOGL_INFO, "Receiving SGsAP-LOCATION-UPDATE-REQUEST without TAI nor " + "E-CGI IEs, fast fallback GERAN->EUTRAN won't be possible!\n"); + } + /* Perform actual location update */ memcpy(vlr_sgs_cfg.timer, sgc->sgs->cfg.timer, sizeof(vlr_sgs_cfg.timer)); memcpy(vlr_sgs_cfg.counter, sgc->sgs->cfg.counter, sizeof(vlr_sgs_cfg.counter)); rc = vlr_sgs_loc_update(gsm_network->vlr, &vlr_sgs_cfg, sgs_tx_loc_upd_resp_cb, sgs_iface_tx_paging, - sgs_tx_mm_info_cb, mme_name, type, imsi, &new_lai); + sgs_tx_mm_info_cb, mme_name, type, imsi, &new_lai, last_eutran_plmn); if (rc != 0) { resp = gsm29118_create_lu_rej(imsi, SGSAP_SGS_CAUSE_IMSI_UNKNOWN, NULL); sgs_tx(sgc, resp); @@ -905,6 +925,26 @@ static int sgs_rx_csfb_ind(struct sgs_connection *sgc, struct msgb *msg, const s if (!vsub) return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_IMSI_UNKNOWN, msg, SGSAP_IE_IMSI); + /* 3GPP TS 23.272 sec 4.3.3 (CSFB): + * "During the SGs location update procedure, obtaining the last used LTE PLMN ID via TAI" + */ + vsub->sgs.last_eutran_plmn_present = TLVP_PRES_LEN(tp, SGSAP_IE_EUTRAN_CGI, 3); + if (TLVP_PRES_LEN(tp, SGSAP_IE_TAI, 3)) { + vsub->sgs.last_eutran_plmn_present = true; + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_TAI), &vsub->sgs.last_eutran_plmn); + /* TODO: we could also gather the TAC from here, but we don't need it yet */ + } else if (TLVP_PRES_LEN(tp, SGSAP_IE_EUTRAN_CGI, 3)) { + /* Since TAI is optional, let's try harder getting Last Used + * E-UTRAN PLMN ID by fetching it from E-UTRAN CGI */ + vsub->sgs.last_eutran_plmn_present = true; + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_EUTRAN_CGI), &vsub->sgs.last_eutran_plmn); + /* TODO: we could also gather the ECI from here, but we don't need it yet */ + } else if (!vsub->sgs.last_eutran_plmn_present) { + LOGSGC(sgc, LOGL_INFO, "Receiving SGsAP-MO-CSFB-INDICATION without TAI nor " + "E-CGI IEs, and they are not known from previous SGsAP-LOCATION-UPDATE-REQUEST. " + "Fast fallback GERAN->EUTRAN won't be possible!\n"); + } + /* Check for lingering connections */ subscr_conn_toss(vsub); diff --git a/src/libvlr/vlr.c b/src/libvlr/vlr.c index 33d6331c0..02aceef8e 100644 --- a/src/libvlr/vlr.c +++ b/src/libvlr/vlr.c @@ -476,6 +476,23 @@ void vlr_subscr_set_msisdn(struct vlr_subscr *vsub, const char *msisdn) vsub->imsi, vsub->msisdn); } +void vlr_subscr_set_last_used_eutran_plmn_id(struct vlr_subscr *vsub, + const struct osmo_plmn_id *last_eutran_plmn) +{ + if (!vsub) + return; + if (last_eutran_plmn) { + vsub->sgs.last_eutran_plmn_present = true; + memcpy(&vsub->sgs.last_eutran_plmn, last_eutran_plmn, sizeof(*last_eutran_plmn)); + } else { + vsub->sgs.last_eutran_plmn_present = false; + } + DEBUGP(DVLR, "set Last E-UTRAN PLMN ID on subscriber: %s\n", + vsub->sgs.last_eutran_plmn_present ? + osmo_plmn_name(&vsub->sgs.last_eutran_plmn) : + "(none)"); +} + bool vlr_subscr_matches_imsi(struct vlr_subscr *vsub, const char *imsi) { return vsub && imsi && vsub->imsi[0] && !strcmp(vsub->imsi, imsi); diff --git a/src/libvlr/vlr_sgs.c b/src/libvlr/vlr_sgs.c index 269dda64b..565988621 100644 --- a/src/libvlr/vlr_sgs.c +++ b/src/libvlr/vlr_sgs.c @@ -68,11 +68,12 @@ void vlr_sgs_reset(struct vlr_instance *vlr) * \param[in] type location update type (normal or IMSI attach). * \param[in] imsi mobile identity (IMSI). * \param[in] new_lai identifier of the new location area. + * \param[in] last_eutran_plnm_id Last E-UTRAN PLMN ID (can be NULL). * \returns 0 in case of success, -EINVAL in case of error. */ int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg, vlr_sgs_lu_response_cb_t response_cb, vlr_sgs_lu_paging_cb_t paging_cb, vlr_sgs_lu_mminfo_cb_t mminfo_cb, char *mme_name, enum vlr_lu_type type, const char *imsi, - struct osmo_location_area_id *new_lai) + struct osmo_location_area_id *new_lai, struct osmo_plmn_id *last_eutran_plmn) { struct vlr_subscr *vsub = NULL; @@ -93,6 +94,7 @@ int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg, vsub->sgs.paging_cb = paging_cb; vsub->sgs.mminfo_cb = mminfo_cb; vlr_subscr_set_imsi(vsub, imsi); + vlr_subscr_set_last_used_eutran_plmn_id(vsub, last_eutran_plmn); osmo_strlcpy(vsub->sgs.mme_name, mme_name, sizeof(vsub->sgs.mme_name)); osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_LU_FROM_MME, NULL);