diff options
author | Harald Welte <laforge@gnumonks.org> | 2018-12-03 11:00:04 +0100 |
---|---|---|
committer | Philipp Maier <pmaier@sysmocom.de> | 2019-02-04 13:36:26 +0100 |
commit | 0df904dea9106587f40ec379e9cc05ea251beb7e (patch) | |
tree | 02ccf5ec37b6633677153892dee6b73a1724465f /src/libvlr | |
parent | c7de62cc53fa6ad985015403dd9af8f1627136a0 (diff) |
Add SGs Interface
Add an SGs interface (3GPP TS 29.118) to osmo-msc in order to support
SMS tunneling and Circuit Switched Fallback (CSFB)
Change-Id: I73359925fc1ca72b33a1466e6ac41307f2f0b11d
Related: OS#3615
Diffstat (limited to 'src/libvlr')
-rw-r--r-- | src/libvlr/Makefile.am | 4 | ||||
-rw-r--r-- | src/libvlr/vlr.c | 52 | ||||
-rw-r--r-- | src/libvlr/vlr_lu_fsm.c | 7 | ||||
-rw-r--r-- | src/libvlr/vlr_sgs.c | 331 | ||||
-rw-r--r-- | src/libvlr/vlr_sgs_fsm.c | 386 | ||||
-rw-r--r-- | src/libvlr/vlr_sgs_fsm.h | 42 |
6 files changed, 816 insertions, 6 deletions
diff --git a/src/libvlr/Makefile.am b/src/libvlr/Makefile.am index dcae1c7cf..14cd30212 100644 --- a/src/libvlr/Makefile.am +++ b/src/libvlr/Makefile.am @@ -8,6 +8,7 @@ AM_CFLAGS= \ $(LIBOSMOGSUPCLIENT_CFLAGS) \ $(LIBOSMOABIS_CFLAGS) \ $(LIBOSMORANAP_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ $(COVERAGE_CFLAGS) \ $(NULL) @@ -16,6 +17,7 @@ noinst_HEADERS = \ vlr_auth_fsm.h \ vlr_core.h \ vlr_lu_fsm.h \ + vlr_sgs_fsm.h \ $(NULL) noinst_LIBRARIES = libvlr.a @@ -25,4 +27,6 @@ libvlr_a_SOURCES = \ vlr_access_req_fsm.c \ vlr_auth_fsm.c \ vlr_lu_fsm.c \ + vlr_sgs.c \ + vlr_sgs_fsm.c \ $(NULL) diff --git a/src/libvlr/vlr.c b/src/libvlr/vlr.c index 451c5217d..c5b3c8036 100644 --- a/src/libvlr/vlr.c +++ b/src/libvlr/vlr.c @@ -30,6 +30,7 @@ #include <osmocom/gsm/ipa.h> #include <osmocom/msc/gsm_subscriber.h> #include <osmocom/gsupclient/gsup_client.h> +#include <osmocom/msc/vlr_sgs.h> #include <osmocom/msc/vlr.h> #include <osmocom/msc/debug.h> @@ -42,6 +43,7 @@ #include "vlr_auth_fsm.h" #include "vlr_lu_fsm.h" #include "vlr_access_req_fsm.h" +#include "vlr_sgs_fsm.h" #define SGSN_SUBSCR_MAX_RETRIES 3 #define SGSN_SUBSCR_RETRY_INTERVAL 10 @@ -262,6 +264,11 @@ static struct vlr_subscr *_vlr_subscr_alloc(struct vlr_instance *vlr) INIT_LLIST_HEAD(&vsub->cs.requests); INIT_LLIST_HEAD(&vsub->ps.pdp_list); + /* Create an SGs FSM, which is needed to control CSFB, + * in cases where CSFB/SGs is not in use, this FSM will + * just do nothing. (see also: sgs_iface.c) */ + vlr_sgs_fsm_create(vsub); + llist_add_tail(&vsub->list, &vlr->subscribers); return vsub; } @@ -307,6 +314,13 @@ void vlr_subscr_free(struct vlr_subscr *vsub) { llist_del(&vsub->list); DEBUGP(DREF, "freeing VLR subscr %s\n", vlr_subscr_name(vsub)); + + /* Make sure SGs timer Ts5 is removed */ + osmo_timer_del(&vsub->sgs.Ts5); + + /* Remove SGs FSM (see also: sgs_iface.c) */ + vlr_sgs_fsm_remove(vsub); + talloc_free(vsub); } @@ -854,7 +868,13 @@ static int vlr_subscr_handle_isd_req(struct vlr_subscr *vsub, static int vlr_subscr_handle_lu_res(struct vlr_subscr *vsub, const struct osmo_gsup_message *gsup) { - if (!vsub->lu_fsm) { + struct sgs_lu_response sgs_lu_response; + bool sgs_lu_in_progress = false; + + if (vsub->sgs_fsm->state == SGS_UE_ST_LA_UPD_PRES) + sgs_lu_in_progress = true; + + if (!vsub->lu_fsm && !sgs_lu_in_progress) { LOGVSUBP(LOGL_ERROR, vsub, "Rx GSUP LU Result " "without LU in progress\n"); return -ENODEV; @@ -865,7 +885,12 @@ static int vlr_subscr_handle_lu_res(struct vlr_subscr *vsub, * nested INSERT SUBSCRIBER DATA transaction */ vlr_subscr_gsup_insert_data(vsub, gsup); - osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES, NULL); + if (sgs_lu_in_progress) { + sgs_lu_response.accepted = true; + sgs_lu_response.vsub = vsub; + vsub->sgs.response_cb(&sgs_lu_response); + } else + osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES, NULL); return 0; } @@ -874,7 +899,13 @@ static int vlr_subscr_handle_lu_res(struct vlr_subscr *vsub, static int vlr_subscr_handle_lu_err(struct vlr_subscr *vsub, const struct osmo_gsup_message *gsup) { - if (!vsub->lu_fsm) { + struct sgs_lu_response sgs_lu_response; + bool sgs_lu_in_progress = false; + + if (vsub->sgs_fsm->state == SGS_UE_ST_LA_UPD_PRES) + sgs_lu_in_progress = true; + + if (!vsub->lu_fsm && !sgs_lu_in_progress) { LOGVSUBP(LOGL_ERROR, vsub, "Rx GSUP LU Error " "without LU in progress\n"); return -ENODEV; @@ -883,9 +914,13 @@ static int vlr_subscr_handle_lu_err(struct vlr_subscr *vsub, LOGVSUBP(LOGL_DEBUG, vsub, "UpdateLocation failed; gmm_cause: %s\n", get_value_string(gsm48_gmm_cause_names, gsup->cause)); - osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES, - (void *)&gsup->cause); - + if (sgs_lu_in_progress) { + sgs_lu_response.accepted = false; + sgs_lu_response.vsub = vsub; + vsub->sgs.response_cb(&sgs_lu_response); + } else + osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES, + (void *)&gsup->cause); return 0; } @@ -1225,6 +1260,9 @@ int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub) vsub->imsi_detached_flag = true; vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION; + /* Inform the UE-SGs FSM that the subscriber has been detached */ + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_DETACH_IND_FROM_UE, NULL); + vlr_subscr_expire(vsub); return 0; @@ -1273,6 +1311,8 @@ struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops) vlr_lu_fsm_init(); /* vlr_access_request_fsm.c */ vlr_parq_fsm_init(); + /* vlr_sgs_fsm.c */ + vlr_sgs_fsm_init(); return vlr; } diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c index a97e97ada..8152d20e8 100644 --- a/src/libvlr/vlr_lu_fsm.c +++ b/src/libvlr/vlr_lu_fsm.c @@ -27,6 +27,7 @@ #include "vlr_core.h" #include "vlr_auth_fsm.h" #include "vlr_lu_fsm.h" +#include "vlr_sgs_fsm.h" #define S(x) (1 << (x)) @@ -362,6 +363,7 @@ static void vlr_lu_compl_fsm_success(struct osmo_fsm_inst *fi) vlr_subscr_get(vsub); } _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_SUCCESS, 0); + vlr_sgs_fsm_update_id(vsub); } static void vlr_lu_compl_fsm_failure(struct osmo_fsm_inst *fi, uint8_t cause) @@ -1055,6 +1057,11 @@ static void lu_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, OSMO_ASSERT(lfp->vsub); + /* At this point we know for which subscriber the location update is, + * we now must inform SGs-UE FSM that we received a location update + * via A, IU or Gs interface. */ + osmo_fsm_inst_dispatch(lfp->vsub->sgs_fsm, SGS_UE_E_RX_LU_FROM_A_IU_GS, NULL); + /* See 3GPP TS 23.012, procedure Retrieve_IMEISV_If_Required */ if ((!vlr->cfg.retrieve_imeisv_early) || (lfp->type == VLR_LU_TYPE_PERIODIC && lfp->vsub->imeisv[0])) { diff --git a/src/libvlr/vlr_sgs.c b/src/libvlr/vlr_sgs.c new file mode 100644 index 000000000..861489227 --- /dev/null +++ b/src/libvlr/vlr_sgs.c @@ -0,0 +1,331 @@ +/* (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/msc/debug.h> +#include <osmocom/msc/vlr.h> +#include <osmocom/msc/vlr_sgs.h> +#include "vlr_sgs_fsm.h" + +const struct value_string sgs_state_timer_names[] = { + {SGS_STATE_TS5, "Ts5"}, + {SGS_STATE_TS6_2, "Ts6-2"}, + {SGS_STATE_TS7, "Ts7"}, + {SGS_STATE_TS11, "Ts11"}, + {SGS_STATE_TS14, "Ts14"}, + {SGS_STATE_TS15, "Ts15"}, + {0, NULL} +}; + +const struct value_string sgs_state_counter_names[] = { + {SGS_STATE_NS7, "Ns7"}, + {SGS_STATE_NS11, "Ns11"}, + {0, NULL} +}; + +/* Reset all SGs-Associations back to zero. + * \param[in] vlr VLR instace. */ +void vlr_sgs_reset(struct vlr_instance *vlr) +{ + struct vlr_subscr *vsub; + + OSMO_ASSERT(vlr); + + LOGP(DVLR, LOGL_INFO, "dropping all SGs associations.\n"); + + llist_for_each_entry(vsub, &vlr->subscribers, list) { + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_RESET_FROM_MME, NULL); + } +} + +/*! Perform an SGs location update. + * \param[in] vlr VLR instace. + * \param[in] cfg SGs interface configuration parameters. + * \param[in] response_cb calback function that is called when LU is done. + * \param[in] paging_cb calback function that is called when LU needs to page. + * \param[in] mminfo_cb calback function that is called to provide MM info to the UE. + * \param[in] mme_name fqdn of the requesting MME (mme-name). + * \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. + * \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 vlr_subscr *vsub = NULL; + + OSMO_ASSERT(response_cb); + OSMO_ASSERT(paging_cb); + OSMO_ASSERT(mminfo_cb); + OSMO_ASSERT(cfg); + OSMO_ASSERT(imsi); + + vsub = vlr_subscr_find_or_create_by_imsi(vlr, imsi, NULL); + if (!vsub) { + LOGP(DSGS, LOGL_ERROR, "VLR subscriber allocation failed\n"); + return -EINVAL; + } + + vsub->sgs.cfg = *cfg; + vsub->sgs.response_cb = response_cb; + vsub->sgs.paging_cb = paging_cb; + vsub->sgs.mminfo_cb = mminfo_cb; + vlr_subscr_set_imsi(vsub, imsi); + 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); + + /* FIXME: Use the "type" type parameter (for what is it useful?) */ + + vsub->sgs.lai = *new_lai; + vsub->cgi.lai = *new_lai; + vsub->cs.lac = vsub->sgs.lai.lac; + + return 0; +} + +/*! Notify that the SGs Location Update accept message has been sent to MME. + * \param[in] vsub VLR subscriber. */ +void vlr_sgs_loc_update_acc_sent(struct vlr_subscr *vsub) +{ + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_LU_ACCEPT, NULL); + + /* FIXME: At this point we need to check the status of Ts5 and if + * it is still running this means the LU has interrupted the paging, + * and we need to start paging again. 3GPP TS 29.118, + * chapter 5.2.3.2 */ +} + +/*! Notify that the SGs Location Update reject message has been sent to MME. + * \param[in] vsub VLR subscriber. */ +void vlr_sgs_loc_update_rej_sent(struct vlr_subscr *vsub) +{ + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_LU_REJECT, NULL); +} + +/*! Perform an SGs IMSI detach. + * \param[in] vsub VLR subscriber. + * \param[in] imsi mobile identity (IMSI). + * \param[in] type datach type. */ +void vlr_sgs_imsi_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_imsi_det_noneps_type type) +{ + struct vlr_subscr *vsub; + enum sgs_ue_fsm_event evt; + vsub = vlr_subscr_find_by_imsi(vlr, imsi); + if (!vsub) + return; + + switch (type) { + case SGSAP_ID_NONEPS_T_EXPLICIT_UE_NONEPS: + evt = SGS_UE_E_RX_DETACH_IND_FROM_UE; + break; + case SGSAP_ID_NONEPS_T_COMBINED_UE_EPS_NONEPS: + case SGSAP_ID_NONEPS_T_IMPLICIT_UE_EPS_NONEPS: + /* FIXME: Is that right? */ + evt = SGS_UE_E_RX_DETACH_IND_FROM_MME; + break; + default: + LOGP(DSGS, LOGL_ERROR, "(sub %s) invalid SGS IMSI detach type, detaching anyway...\n", + vlr_subscr_msisdn_or_name(vsub)); + evt = SGS_UE_E_RX_DETACH_IND_FROM_MME; + break; + } + + osmo_fsm_inst_dispatch(vsub->sgs_fsm, evt, NULL); + vlr_subscr_put(vsub); +} + +/*! Perform an SGs EPS detach. + * \param[in] vsub VLR subscriber. + * \param[in] imsi mobile identity (IMSI). + * \param[in] type datach type. */ +void vlr_sgs_eps_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_imsi_det_eps_type type) +{ + struct vlr_subscr *vsub; + enum sgs_ue_fsm_event evt; + vsub = vlr_subscr_find_by_imsi(vlr, imsi); + if (!vsub) + return; + + switch (type) { + case SGSAP_ID_EPS_T_NETWORK_INITIATED: + evt = SGS_UE_E_RX_DETACH_IND_FROM_MME; + break; + case SGSAP_ID_EPS_T_UE_INITIATED: + evt = SGS_UE_E_RX_DETACH_IND_FROM_UE; + break; + case SGSAP_ID_EPS_T_EPS_NOT_ALLOWED: + evt = SGS_UE_E_RX_DETACH_IND_FROM_MME; + break; + default: + LOGP(DSGS, LOGL_ERROR, "(sub %s) invalid SGS IMSI detach type, detaching anyway...\n", + vlr_subscr_msisdn_or_name(vsub)); + evt = SGS_UE_E_RX_DETACH_IND_FROM_MME; + break; + } + + osmo_fsm_inst_dispatch(vsub->sgs_fsm, evt, NULL); + vlr_subscr_put(vsub); +} + +/*! Perform an SGs TMSI reallocation complete. + * \param[in] vsub VLR subscriber. + * \param[in] imsi mobile identity (IMSI). */ +void vlr_sgs_tmsi_reall_compl(struct vlr_instance *vlr, const char *imsi) +{ + struct vlr_subscr *vsub; + vsub = vlr_subscr_find_by_imsi(vlr, imsi); + if (!vsub) + return; + + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_TMSI_REALLOC, NULL); + vlr_subscr_put(vsub); +} + +/*! Notify that an SGs paging has been rejected by the MME. + * \param[in] vsub VLR subscriber. + * \param[in] imsi mobile identity (IMSI). + * \param[in] cause SGs cause code. */ +void vlr_sgs_pag_rej(struct vlr_instance *vlr, const char *imsi, enum sgsap_sgs_cause cause) +{ + struct vlr_subscr *vsub; + vsub = vlr_subscr_find_by_imsi(vlr, imsi); + if (!vsub) + return; + + /* On the reception of a paging rej the VLR is supposed to stop Ts5, + also 3GPP TS 29.118, chapter 5.1.2.4 */ + osmo_timer_del(&vsub->sgs.Ts5); + LOGP(DSGS, LOGL_DEBUG, "(sub %s) Paging via SGs interface rejected by MME, %s stopped, cause: %s!\n", + vlr_subscr_msisdn_or_name(vsub), vlr_sgs_state_timer_name(SGS_STATE_TS5), sgsap_sgs_cause_name(cause)); + + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_PAGING_FAILURE, &cause); + vlr_subscr_put(vsub); + + /* Balance ref count increment from vlr_sgs_pag() */ + vlr_subscr_put(vsub); +} + +/*! Notify that an SGs paging has been accepted by the MME. + * \param[in] vsub VLR subscriber. + * \param[in] imsi mobile identity (IMSI). */ +void vlr_sgs_pag_ack(struct vlr_instance *vlr, const char *imsi) +{ + struct vlr_subscr *vsub; + vsub = vlr_subscr_find_by_imsi(vlr, imsi); + if (!vsub) + return; + + /* Stop Ts5 and and consider the paging as successful */ + osmo_timer_del(&vsub->sgs.Ts5); + vlr_subscr_put(vsub); + + /* Balance ref count increment from vlr_sgs_pag() */ + vlr_subscr_put(vsub); +} + +/*! Notify that the UE has been marked as unreachable by the MME. + * \param[in] vsub VLR subscriber. + * \param[in] imsi mobile identity (IMSI). + * \param[in] cause SGs cause code. */ +void vlr_sgs_ue_unr(struct vlr_instance *vlr, const char *imsi, enum sgsap_sgs_cause cause) +{ + struct vlr_subscr *vsub; + vsub = vlr_subscr_find_by_imsi(vlr, imsi); + if (!vsub) + return; + + /* On the reception of an UE unreachable the VLR is supposed to stop + * Ts5, also 3GPP TS 29.118, chapter 5.1.2.5 */ + osmo_timer_del(&vsub->sgs.Ts5); + LOGP(DSGS, LOGL_DEBUG, + "(sub %s) Paging via SGs interface not possible, UE unreachable, %s stopped, cause: %s\n", + vlr_subscr_msisdn_or_name(vsub), vlr_sgs_state_timer_name(SGS_STATE_TS5), sgsap_sgs_cause_name(cause)); + + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_SGSAP_UE_UNREACHABLE, &cause); + vlr_subscr_put(vsub); +} + +/* Callback function that is called when an SGs paging request times out */ +static void Ts5_timeout_cb(void *arg) +{ + struct vlr_subscr *vsub = arg; + + /* 3GPP TS 29.118 does not specify a specif action that has to happen + * in case Ts5 times out. The timeout just indicates that the paging + * failed. Other actions may check the status of Ts5 to see if a paging + * is still ongoing or not. */ + + LOGP(DSGS, LOGL_ERROR, "(sub %s) Paging via SGs interface timed out (%s expired)!\n", + vlr_subscr_msisdn_or_name(vsub), vlr_sgs_state_timer_name(SGS_STATE_TS5)); + + /* Balance ref count increment from vlr_sgs_pag() */ + vlr_subscr_put(vsub); + + return; +} + +/*! Notify that a paging message has been sent and a paging is now in progress. + * \param[in] vsub VLR subscriber. */ +void vlr_sgs_pag(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind) +{ + + /* In cases where we have to respawn a paging after an intermitted LU, + * there may e a Ts5 still running. In those cases we have to remove + * the old timer first */ + if (osmo_timer_pending(&vsub->sgs.Ts5)) + osmo_timer_del(&vsub->sgs.Ts5); + + /* Note: 3GPP TS 29.118, chapter 4.2.2 mentions paging in the FSM + * diagram, but paging never causes a state transition except when + * an explicit failure is indicated (MME actively rejects paging). + * Apparantly it is also possible that an LU happens while the paging + * is still ongoing and Ts5 is running. (chapter 5.1.2.3). This means + * that the paging procedure is intended to run in parallel to the + * SGs FSM and given that the benaviour around Ts5 must be implemented + * also separately, to emphasize this separation Ts5 is implemented + * here in and not in vlr_sgs_fsm.c. */ + osmo_timer_setup(&vsub->sgs.Ts5, Ts5_timeout_cb, vsub); + osmo_timer_schedule(&vsub->sgs.Ts5, vsub->sgs.cfg.timer[SGS_STATE_TS5], 0); + + /* Formally 3GPP TS 29.118 defines the sending of a paging request + * as an event, but as far as the VLR is concerned only Ts5 is + * started. */ + osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_PAGING, NULL); + + /* Memorize service type in for the case that the paging must be + * respawned after an LU */ + vsub->sgs.paging_serv_ind = serv_ind; + + /* Ensure that the reference count is increased by one while the + * paging is happening. We will balance this again in vlr_sgs_pag_rej() + * and vlr_sgs_pag_ack(); */ + vlr_subscr_get(vsub); +} + +/*! Check if the SGs interface is currently paging + * \param[in] vsub VLR subscriber. */ +bool vlr_sgs_pag_pend(struct vlr_subscr *vsub) +{ + return osmo_timer_pending(&vsub->sgs.Ts5); +} 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); + } +} diff --git a/src/libvlr/vlr_sgs_fsm.h b/src/libvlr/vlr_sgs_fsm.h new file mode 100644 index 000000000..94f202b59 --- /dev/null +++ b/src/libvlr/vlr_sgs_fsm.h @@ -0,0 +1,42 @@ +/* (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/>. + * + */ + +#pragma once + +enum sgs_ue_fsm_event { + SGS_UE_E_VLR_FAILURE, + SGS_UE_E_RX_RESET_FROM_MME, + SGS_UE_E_RX_DETACH_IND_FROM_MME, + SGS_UE_E_RX_DETACH_IND_FROM_UE, + SGS_UE_E_RX_LU_FROM_A_IU_GS, + SGS_UE_E_RX_PAGING_FAILURE, + SGS_UE_E_RX_ALERT_FAILURE, + SGS_UE_E_RX_LU_FROM_MME, + SGS_UE_E_TX_LU_REJECT, + SGS_UE_E_TX_LU_ACCEPT, + SGS_UE_E_TX_PAGING, + SGS_UE_E_RX_SGSAP_UE_UNREACHABLE, + SGS_UE_E_RX_TMSI_REALLOC, +}; + +void vlr_sgs_fsm_init(void); +void vlr_sgs_fsm_create(struct vlr_subscr *vsub); +void vlr_sgs_fsm_remove(struct vlr_subscr *vsub); +void vlr_sgs_fsm_update_id(struct vlr_subscr *vsub); |