diff options
Diffstat (limited to 'src/libvlr/vlr_sgs_fsm.c')
-rw-r--r-- | src/libvlr/vlr_sgs_fsm.c | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/src/libvlr/vlr_sgs_fsm.c b/src/libvlr/vlr_sgs_fsm.c new file mode 100644 index 000000000..ee1d74804 --- /dev/null +++ b/src/libvlr/vlr_sgs_fsm.c @@ -0,0 +1,386 @@ +/* (C) 2018-2019 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Harald Welte, Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/msc/debug.h> +#include <osmocom/msc/vlr.h> +#include <osmocom/msc/vlr_sgs.h> + +#include "vlr_sgs_fsm.h" +#include "vlr_core.h" + +#define S(x) (1 << (x)) + +static const struct value_string sgs_ue_fsm_event_names[] = { + {SGS_UE_E_VLR_FAILURE, "VLR_FAILURE"}, + {SGS_UE_E_RX_RESET_FROM_MME, "RX_RESET_FROM_MME"}, + {SGS_UE_E_RX_DETACH_IND_FROM_MME, "RX_DETACH_IND_FROM_MME"}, + {SGS_UE_E_RX_DETACH_IND_FROM_UE, "RX_DETACH_IND_FROM_UE"}, /* vlr.c */ + {SGS_UE_E_RX_LU_FROM_A_IU_GS, "RX_LU_FROM_A_Iu_Gs"}, /* vlr_lu_fsm.c */ + {SGS_UE_E_RX_PAGING_FAILURE, "RX_PAGING_FAILURE"}, + {SGS_UE_E_RX_ALERT_FAILURE, "RX_ALERT_FAILURE"}, + {SGS_UE_E_RX_LU_FROM_MME, "RX_LU_FROM_MME"}, + {SGS_UE_E_TX_LU_REJECT, "TX_LU_REJECT"}, + {SGS_UE_E_TX_LU_ACCEPT, "TX_LU_ACCEPT"}, + {SGS_UE_E_TX_PAGING, "TX_PAGING"}, + {SGS_UE_E_RX_SGSAP_UE_UNREACHABLE, "RX_SGSAP_UE_UNREACH"}, + {SGS_UE_E_RX_TMSI_REALLOC, "RX_TMSI_REALLOC"}, + {0, NULL} +}; + +/* Initiate location update and change to SGS_UE_ST_LA_UPD_PRES state */ +static void perform_lu(struct osmo_fsm_inst *fi) +{ + struct vlr_subscr *vsub = fi->priv; + int rc; + osmo_fsm_inst_state_chg(fi, SGS_UE_ST_LA_UPD_PRES, 0, 0); + vsub->ms_not_reachable_flag = false; + + /* Note: At the moment we allocate a new TMSI on each LU. */ + rc = vlr_subscr_alloc_tmsi(vsub); + if (rc != 0) + LOGPFSML(fi, LOGL_ERROR, "(sub %s) VLR LU tmsi allocation failed\n", vlr_subscr_name(vsub)); + + rc = vlr_subscr_req_lu(vsub); + if (rc != 0) + LOGPFSML(fi, LOGL_ERROR, "(sub %s) HLR LU request failed\n", vlr_subscr_name(vsub)); +} + +/* Send the SGs Association to NULL state immediately */ +static void to_null(struct osmo_fsm_inst *fi) +{ + struct vlr_subscr *vsub = fi->priv; + osmo_fsm_inst_state_chg(fi, SGS_UE_ST_NULL, 0, 0); + + /* Note: This is only relevent for cases where we are in the middle + * of an TMSI reallocation procedure. Should a failure of some sort + * put us to NULL state, we have to free the pending TMSI */ + vsub->tmsi_new = GSM_RESERVED_TMSI; + + /* Make sure any ongoing paging is aborted. */ + vsub->cs.is_paging = false; + + /* Ensure that Ts5 (pending paging via SGs) is deleted */ + if (vlr_sgs_pag_pend(vsub)) + osmo_timer_del(&vsub->sgs.Ts5); +} + +/* Respawn a pending paging (Timer is reset and a new paging request is sent) */ +static void respawn_paging(struct vlr_subscr *vsub) +{ + if (vlr_sgs_pag_pend(vsub)) { + + /* Delete the old paging timer first. */ + osmo_timer_del(&vsub->sgs.Ts5); + + /* Issue a fresh paging request */ + vsub->sgs.paging_cb(vsub, vsub->sgs.paging_serv_ind); + } +} + +/* Figure 4.2.2.1 SGs-NULL */ +static void sgs_ue_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case SGS_UE_E_RX_LU_FROM_MME: + perform_lu(fi); + break; + case SGS_UE_E_TX_PAGING: + /* do nothing */ + break; + case SGS_UE_E_RX_PAGING_FAILURE: + /* do nothing */ + break; + default: + OSMO_ASSERT(0); + } +} + +/* Figure 4.2.2.1 SGs-LA-UPDATE-PRESENT */ +static void sgs_ue_fsm_lau_present(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vlr_subscr *vsub = fi->priv; + enum sgsap_sgs_cause *cause = NULL; + + switch (event) { + case SGS_UE_E_TX_LU_ACCEPT: + vsub->conf_by_radio_contact_ind = true; + vsub->sub_dataconf_by_hlr_ind = true; + vsub->loc_conf_in_hlr_ind = true; + vsub->la_allowed = true; + vsub->imsi_detached_flag = false; + vsub->lu_complete = true; + vlr_sgs_fsm_update_id(vsub); + vsub->cs.attached_via_ran = OSMO_RAT_EUTRAN_SGS; + + /* Check if we expect a TMSI REALLOCATION COMPLETE message from the MME + * by checking the tmsi_new flag. If this flag is not GSM_RESERVED_TMSI + * we know that we have a TMSI pending and need to wait for the MME + * to acknowlege first */ + if (vsub->tmsi_new != GSM_RESERVED_TMSI) { + osmo_fsm_inst_state_chg(fi, SGS_UE_ST_ASSOCIATED, vsub->sgs.cfg.timer[SGS_STATE_TS6_2], + SGS_STATE_TS6_2); + } else { + /* Trigger sending of an MM information request */ + vsub->sgs.mminfo_cb(vsub); + + /* In cases where the LU has interrupted the paging, respawn the paging now, + * See also: 3GPP TS 29.118, chapter 5.2.3.2 Location update response */ + if (vlr_sgs_pag_pend(vsub)) + respawn_paging(vsub); + + osmo_fsm_inst_state_chg(fi, SGS_UE_ST_ASSOCIATED, 0, 0); + } + + break; + case SGS_UE_E_RX_PAGING_FAILURE: + cause = data; + if (*cause == SGSAP_SGS_CAUSE_MT_CSFB_REJ_USER) + break; + to_null(fi); + break; + case SGS_UE_E_TX_LU_REJECT: + case SGS_UE_E_RX_ALERT_FAILURE: + to_null(fi); + break; + case SGS_UE_E_TX_PAGING: + /* do nothing */ + break; + default: + OSMO_ASSERT(0); + break; + } +} + +/* Figure 4.2.2.1 SGs-ASSOCIATED */ +static void sgs_ue_fsm_associated(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vlr_subscr *vsub = fi->priv; + enum sgsap_sgs_cause *cause = NULL; + + switch (event) { + case SGS_UE_E_TX_PAGING: + /* do nothing */ + break; + case SGS_UE_E_RX_TMSI_REALLOC: + if (vsub->tmsi_new == GSM_RESERVED_TMSI) { + LOGPFSML(fi, LOGL_ERROR, + "(sub %s) TMSI reallocation completed at the MME, but no TMSI reallocation ordered.\n", + vlr_subscr_msisdn_or_name(vsub)); + } + + vsub->tmsi = vsub->tmsi_new; + vsub->tmsi_new = GSM_RESERVED_TMSI; + + /* Trigger sending of MM information */ + vsub->sgs.mminfo_cb(vsub); + + /* In cases where the LU has interrupted the paging, respawn the paging now, + * See also: 3GPP TS 29.118, chapter 5.2.3.2 Location update response */ + if (vlr_sgs_pag_pend(vsub)) + respawn_paging(vsub); + + /* Note: We are already in SGS_UE_ST_ASSOCIATED but the + * transition that lead us here had is guarded with Ts6-1, + * wo we change the state now once more without timeout + * to ensure the timer is stopped */ + osmo_fsm_inst_state_chg(fi, SGS_UE_ST_ASSOCIATED, 0, 0); + break; + case SGS_UE_E_RX_SGSAP_UE_UNREACHABLE: + /* do nothing */ + break; + case SGS_UE_E_RX_PAGING_FAILURE: + cause = data; + if (*cause == SGSAP_SGS_CAUSE_MT_CSFB_REJ_USER) + break; + to_null(fi); + case SGS_UE_E_RX_ALERT_FAILURE: + to_null(fi); + break; + case SGS_UE_E_RX_LU_FROM_MME: + perform_lu(fi); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +/* Figure 4.2.2.1 From any of the three states (at the VLR) */ +static void sgs_ue_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vlr_subscr *vsub = fi->priv; + + switch (event) { + case SGS_UE_E_RX_DETACH_IND_FROM_MME: + case SGS_UE_E_RX_DETACH_IND_FROM_UE: + vsub->imsi_detached_flag = true; + vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION; + /* See 5.4.3 and 5.5.3 */ + to_null(fi); + break; + case SGS_UE_E_RX_RESET_FROM_MME: + /* See also 3GPP TS 29.118, chapter 5.7.2.1 VLR Reset Initiation */ + vsub->conf_by_radio_contact_ind = false; + to_null(fi); + break; + case SGS_UE_E_VLR_FAILURE: + case SGS_UE_E_RX_LU_FROM_A_IU_GS: + to_null(fi); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static int sgs_ue_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct vlr_subscr *vsub = fi->priv; + switch (fi->T) { + + case SGS_STATE_TS6_2: + /* Failed TMSI reallocation procedure, deallocate all TMSI + * information, but don't change the SGs association state. */ + vsub->tmsi_new = GSM_RESERVED_TMSI; + vsub->tmsi = GSM_RESERVED_TMSI; + break; + default: + /* Unhandled timer */ + OSMO_ASSERT(false); + break; + } + return 0; +} + +static const struct osmo_fsm_state sgs_ue_fsm_states[] = { + [SGS_UE_ST_NULL] = { + .name = "SGs-NULL", + .action = sgs_ue_fsm_null, + .in_event_mask = 0 + | S(SGS_UE_E_RX_LU_FROM_MME) + | S(SGS_UE_E_TX_PAGING) + | S(SGS_UE_E_RX_PAGING_FAILURE) + , + .out_state_mask = 0 + | S(SGS_UE_ST_NULL) + | S(SGS_UE_ST_LA_UPD_PRES) + , + }, + [SGS_UE_ST_LA_UPD_PRES] = { + .name = "SGs-LA-UPDATE-PRESENT", + .action = sgs_ue_fsm_lau_present, + .in_event_mask = 0 + | S(SGS_UE_E_TX_LU_ACCEPT) + | S(SGS_UE_E_TX_LU_REJECT) + | S(SGS_UE_E_TX_PAGING) + | S(SGS_UE_E_RX_PAGING_FAILURE) + | S(SGS_UE_E_RX_ALERT_FAILURE) + , + .out_state_mask = 0 + | S(SGS_UE_ST_NULL) + | S(SGS_UE_ST_ASSOCIATED) + | S(SGS_UE_ST_LA_UPD_PRES) + , + }, + [SGS_UE_ST_ASSOCIATED] = { + .name = "SGs-ASSOCIATED", + .action = sgs_ue_fsm_associated, + .in_event_mask = 0 + | S(SGS_UE_E_TX_PAGING) + | S(SGS_UE_E_RX_TMSI_REALLOC) + | S(SGS_UE_E_RX_SGSAP_UE_UNREACHABLE) + | S(SGS_UE_E_RX_PAGING_FAILURE) + | S(SGS_UE_E_RX_ALERT_FAILURE) + | S(SGS_UE_E_RX_LU_FROM_MME) + , + .out_state_mask = 0 + | S(SGS_UE_ST_NULL) + | S(SGS_UE_ST_ASSOCIATED) + | S(SGS_UE_ST_LA_UPD_PRES) + , + }, +}; + +static struct osmo_fsm sgs_ue_fsm = { + .name = "SGs-UE", + .states = sgs_ue_fsm_states, + .num_states = ARRAY_SIZE(sgs_ue_fsm_states), + .allstate_event_mask = S(SGS_UE_E_RX_RESET_FROM_MME) | + S(SGS_UE_E_VLR_FAILURE) | S(SGS_UE_E_RX_DETACH_IND_FROM_MME) | S(SGS_UE_E_RX_DETACH_IND_FROM_UE) | + S(SGS_UE_E_RX_LU_FROM_A_IU_GS), + .allstate_action = sgs_ue_fsm_allstate, + .timer_cb = sgs_ue_fsm_timer_cb, + .log_subsys = DSGS, + .event_names = sgs_ue_fsm_event_names, +}; + +/*! Initalize/Register SGs FSM in osmo-fsm subsystem */ +void vlr_sgs_fsm_init(void) +{ + if (osmo_fsm_find_by_name(sgs_ue_fsm.name) != &sgs_ue_fsm) + osmo_fsm_register(&sgs_ue_fsm); +} + +/*! Crate SGs FSM in struct vlr_subscr. + * \param[in] vsub VLR subscriber for which the SGs FSM should be created. */ +void vlr_sgs_fsm_create(struct vlr_subscr *vsub) +{ + char interim_fsm_id[256]; + static unsigned int fsm_id_num = 0; + + /* An SGSs FSM must not be created twice! */ + OSMO_ASSERT(!vsub->sgs_fsm); + + snprintf(interim_fsm_id, sizeof(interim_fsm_id), "num:%u", fsm_id_num); + + vsub->sgs_fsm = osmo_fsm_inst_alloc(&sgs_ue_fsm, vsub, vsub, LOGL_INFO, interim_fsm_id); + OSMO_ASSERT(vsub->sgs_fsm); + + osmo_fsm_inst_state_chg(vsub->sgs_fsm, SGS_UE_ST_NULL, 0, 0); + + fsm_id_num++; +} + +/*! Remove SGs FSM from struct vlr_subscr. + * \param[in] vsub VLR subscriber from which the SGs FSM should be removed. */ +void vlr_sgs_fsm_remove(struct vlr_subscr *vsub) +{ + /* An SGSs FSM must exist! */ + OSMO_ASSERT(vsub->sgs_fsm); + + osmo_fsm_inst_state_chg(vsub->sgs_fsm, SGS_UE_ST_NULL, 0, 0); + osmo_fsm_inst_term(vsub->sgs_fsm, OSMO_FSM_TERM_REGULAR, NULL); + vsub->sgs_fsm = NULL; +} + +/*! Update the ID of the SGs FSM with the subscriber IMSI + * \param[in] vsub VLR subscriber to update. */ +void vlr_sgs_fsm_update_id(struct vlr_subscr *vsub) +{ + char fsm_id[256]; + + if (strlen(vsub->imsi) > 0) { + snprintf(fsm_id, sizeof(fsm_id), "imsi:%s", vsub->imsi); + osmo_fsm_inst_update_id(vsub->sgs_fsm, fsm_id); + } +} |