lynxis lazus has uploaded this change for review.

View Change

add libvlr based on MSC commit 25362cc295036a4671804db9003c3d22a88309d2

Change-Id: I221114e00e6f338a75ae0dd7ac285e262b23635d
---
M configure.ac
A include/osmocom/vlr/Makefile.am
A include/osmocom/vlr/vlr.h
A include/osmocom/vlr/vlr_sgs.h
M src/Makefile.am
A src/libvlr/Makefile.am
A src/libvlr/vlr.c
A src/libvlr/vlr_access_req_fsm.c
A src/libvlr/vlr_access_req_fsm.h
A src/libvlr/vlr_auth_fsm.c
A src/libvlr/vlr_auth_fsm.h
A src/libvlr/vlr_core.h
A src/libvlr/vlr_logging.c
A src/libvlr/vlr_lu_fsm.c
A src/libvlr/vlr_lu_fsm.h
A src/libvlr/vlr_sgs.c
A src/libvlr/vlr_sgs_fsm.c
A src/libvlr/vlr_sgs_fsm.h
M src/sgsn/Makefile.am
M tests/gprs_routing_area/Makefile.am
M tests/sgsn/Makefile.am
21 files changed, 6,729 insertions(+), 0 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-sgsn refs/changes/60/39560/1
diff --git a/configure.ac b/configure.ac
index 17a0236..fb45d4a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -237,6 +237,7 @@
include/osmocom/gtphub/Makefile
include/osmocom/sgsn/Makefile
src/Makefile
+ src/libvlr/Makefile
src/gprs/Makefile
src/sgsn/Makefile
src/gtphub/Makefile
diff --git a/include/osmocom/vlr/Makefile.am b/include/osmocom/vlr/Makefile.am
new file mode 100644
index 0000000..f47da40
--- /dev/null
+++ b/include/osmocom/vlr/Makefile.am
@@ -0,0 +1,4 @@
+noinst_HEADERS = \
+ vlr.h \
+ vlr_sgs.h \
+ $(NULL)
diff --git a/include/osmocom/vlr/vlr.h b/include/osmocom/vlr/vlr.h
new file mode 100644
index 0000000..03b76f3
--- /dev/null
+++ b/include/osmocom/vlr/vlr.h
@@ -0,0 +1,506 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/use_count.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include <osmocom/vlr/vlr_sgs.h>
+
+struct log_target;
+struct osmo_mobile_identity;
+
+#define VLR_SUBSCRIBER_NO_EXPIRATION 0
+#define VLR_SUBSCRIBER_LU_EXPIRATION_INTERVAL 60 /* in seconds */
+
+#define VSUB_USE_ATTACHED "attached"
+
+/* from 3s to 10s */
+#define GSM_29002_TIMER_S 10
+/* from 15s to 30s */
+#define GSM_29002_TIMER_M 30
+/* from 1min to 10min */
+#define GSM_29002_TIMER_ML (10*60)
+/* from 28h to 38h */
+#define GSM_29002_TIMER_L (32*60*60)
+
+/* VLR subscriber authentication state */
+enum vlr_subscr_auth_state {
+ /* subscriber needs to be authenticated */
+ VLR_SUB_AS_NEEDS_AUTH,
+ /* waiting for AuthInfo from HLR/AUC */
+ VLR_SUB_AS_NEEDS_AUTH_WAIT_AI,
+ /* waiting for response from subscriber */
+ VLR_SUB_AS_WAIT_RESP,
+ /* successfully authenticated */
+ VLR_SUB_AS_AUTHENTICATED,
+ /* subscriber needs re-sync */
+ VLR_SUB_AS_NEEDS_RESYNC,
+ /* waiting for AuthInfo with ReSync */
+ VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC,
+ /* waiting for response from subscr, resync case */
+ VLR_SUB_AS_WAIT_RESP_RESYNC,
+ /* waiting for IMSI from subscriber */
+ VLR_SUB_AS_WAIT_ID_IMSI,
+ /* authentication has failed */
+ VLR_SUB_AS_AUTH_FAILED,
+};
+
+enum vlr_lu_event {
+ VLR_ULA_E_UPDATE_LA, /* Initial trigger (LU from MS) */
+ VLR_ULA_E_SEND_ID_ACK, /* Result of Send-ID from PVLR */
+ VLR_ULA_E_SEND_ID_NACK, /* Result of Send-ID from PVLR */
+ VLR_ULA_E_AUTH_SUCCESS, /* Successful result of auth procedure */
+ VLR_ULA_E_AUTH_NO_INFO, /* HLR returned SAI NACK, possibly continue without auth */
+ VLR_ULA_E_AUTH_FAILURE, /* Auth procedure failed */
+ VLR_ULA_E_CIPH_RES, /* Result of Ciphering Mode Command */
+ VLR_ULA_E_ID_IMSI, /* IMSI received from MS */
+ VLR_ULA_E_ID_IMEI, /* IMEI received from MS */
+ VLR_ULA_E_ID_IMEISV, /* IMEISV received from MS */
+ VLR_ULA_E_HLR_IMEI_ACK, /* Check_IMEI_VLR result from HLR */
+ VLR_ULA_E_HLR_IMEI_NACK,/* Check_IMEI_VLR result from HLR */
+ VLR_ULA_E_HLR_LU_RES, /* HLR UpdateLocation result */
+ VLR_ULA_E_UPD_HLR_COMPL,/* UpdatE_HLR_VLR result */
+ VLR_ULA_E_LU_COMPL_SUCCESS,/* Location_Update_Completion_VLR result */
+ VLR_ULA_E_LU_COMPL_FAILURE,/* Location_Update_Completion_VLR result */
+ VLR_ULA_E_NEW_TMSI_ACK, /* TMSI Reallocation Complete */
+};
+
+enum vlr_ciph_result_cause {
+ VLR_CIPH_REJECT, /* ? */
+ VLR_CIPH_COMPL,
+};
+
+struct vlr_auth_tuple {
+ int use_count;
+ int key_seq;
+ struct osmo_auth_vector vec;
+};
+#define VLR_KEY_SEQ_INVAL 7 /* GSM 04.08 - 10.5.1.2 */
+
+
+enum vlr_subscr_security_context {
+ VLR_SEC_CTX_NONE,
+ VLR_SEC_CTX_GSM,
+ VLR_SEC_CTX_UMTS,
+};
+
+enum vlr_lu_type {
+ VLR_LU_TYPE_PERIODIC,
+ VLR_LU_TYPE_IMSI_ATTACH,
+ VLR_LU_TYPE_REGULAR,
+};
+
+#define OSMO_LBUF_DECL(name, xlen) \
+ struct { \
+ uint8_t buf[xlen]; \
+ size_t len; \
+ } name
+
+struct sgsn_mm_ctx;
+struct vlr_instance;
+
+#define VLR_NAME_LENGTH 160
+
+/* The VLR subscriber is the part of the GSM subscriber state in VLR (CS) or
+ * SGSN (PS), particularly while interacting with the HLR via GSUP */
+struct vlr_subscr {
+ struct llist_head list;
+ struct vlr_instance *vlr;
+
+ /* TODO either populate from HLR or drop this completely? */
+ long long unsigned int id;
+
+ /* Data from HLR */ /* 3GPP TS 23.008 */
+ /* Always use vlr_subscr_set_imsi() to write to imsi[] */
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1]; /* 2.1.1.1 */
+ char msisdn[GSM23003_MSISDN_MAX_DIGITS+1]; /* 2.1.2 */
+ char name[VLR_NAME_LENGTH+1]; /* proprietary */
+ OSMO_LBUF_DECL(hlr, 16); /* 2.4.7 */
+ uint32_t age_indicator; /* 2.17.1 */
+
+ /* Authentication Data */
+ struct vlr_auth_tuple auth_tuples[5]; /* 2.3.1-2.3.4 */
+ struct vlr_auth_tuple *last_tuple;
+ enum vlr_subscr_security_context sec_ctx;
+
+ /* Data local to VLR is below */
+ uint32_t tmsi; /* 2.1.4 */
+ /* Newly allocated TMSI that was not yet acked by MS */
+ uint32_t tmsi_new;
+
+ struct osmo_cell_global_id cgi; /* 2.4.16 */
+
+ char imeisv[GSM23003_IMEISV_NUM_DIGITS+1]; /* 2.2.3 */
+ char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1]; /* 2.1.9 */
+ bool imsi_detached_flag; /* 2.7.1 */
+ bool conf_by_radio_contact_ind; /* 2.7.4.1 */
+ bool sub_dataconf_by_hlr_ind; /* 2.7.4.2 */
+ bool loc_conf_in_hlr_ind; /* 2.7.4.3 */
+ bool dormant_ind; /* 2.7.8 */
+ bool cancel_loc_rx; /* 2.7.8A */
+ bool ms_not_reachable_flag; /* 2.10.2 (MNRF) */
+ bool la_allowed;
+
+ struct osmo_use_count use_count;
+ struct osmo_use_count_entry use_count_buf[8];
+ int32_t max_total_use_count;
+
+ struct osmo_fsm_inst *lu_fsm;
+ struct osmo_fsm_inst *auth_fsm;
+ struct osmo_fsm_inst *proc_arq_fsm;
+ struct osmo_fsm_inst *sgs_fsm;
+
+ bool lu_complete;
+ time_t expire_lu;
+
+ void *msc_conn_ref;
+
+ /* PS (SGSN) specific parts */
+ struct {
+ struct llist_head pdp_list;
+ } ps;
+ /* CS (NITB/CSCN) specific parts */
+ struct {
+ /* pending requests */
+ bool is_paging;
+ struct osmo_timer_list paging_response_timer;
+ /* list of struct subscr_request */
+ struct llist_head requests;
+ uint8_t lac;
+ enum osmo_rat_type attached_via_ran;
+ } cs;
+ /* SGs (MME) specific parts */
+ struct {
+ struct vlr_sgs_cfg cfg;
+ char mme_name[SGS_MME_NAME_LEN + 1];
+ struct osmo_location_area_id lai;
+ vlr_sgs_lu_response_cb_t response_cb;
+ vlr_sgs_lu_paging_cb_t paging_cb;
+ 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;
+};
+
+enum vlr_ciph {
+ VLR_CIPH_NONE = 0, /*< A5/0, no encryption */
+ VLR_CIPH_A5_1 = 1, /*< A5/1, encryption */
+ VLR_CIPH_A5_2 = 2, /*< A5/2, deprecated export-grade encryption */
+ VLR_CIPH_A5_3 = 3, /*< A5/3, 'new secure' encryption */
+};
+
+static inline uint8_t vlr_ciph_to_gsm0808_alg_id(enum vlr_ciph ciph)
+{
+ switch (ciph) {
+ case VLR_CIPH_NONE:
+ return GSM0808_ALG_ID_A5_0;
+ case VLR_CIPH_A5_1:
+ return GSM0808_ALG_ID_A5_1;
+ case VLR_CIPH_A5_2:
+ return GSM0808_ALG_ID_A5_2;
+ case VLR_CIPH_A5_3:
+ return GSM0808_ALG_ID_A5_3;
+ default:
+ return GSM0808_ALG_ID_A5_7;
+ }
+}
+
+struct vlr_ops {
+ /* encode + transmit an AUTH REQ towards the MS.
+ * \param[in] at auth tuple providing rand, key_seq and autn.
+ * \param[in] send_autn True to send AUTN, for r99 UMTS auth.
+ */
+ int (*tx_auth_req)(void *msc_conn_ref, struct vlr_auth_tuple *at,
+ bool send_autn);
+ /* encode + transmit an AUTH REJECT towards the MS */
+ int (*tx_auth_rej)(void *msc_conn_ref);
+
+ /* encode + transmit an IDENTITY REQUEST towards the MS */
+ int (*tx_id_req)(void *msc_conn_ref, uint8_t mi_type);
+
+ int (*tx_lu_acc)(void *msc_conn_ref, uint32_t send_tmsi, enum vlr_lu_type lu_type);
+ int (*tx_lu_rej)(void *msc_conn_ref, enum gsm48_reject_value cause, enum vlr_lu_type lu_type);
+ int (*tx_cm_serv_acc)(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type);
+ int (*tx_cm_serv_rej)(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type,
+ enum gsm48_reject_value cause);
+
+ int (*set_ciph_mode)(void *msc_conn_ref, bool umts_aka, bool retrieve_imeisv);
+
+ int (*tx_common_id)(void *msc_conn_ref);
+
+ int (*tx_mm_info)(void *msc_conn_ref);
+
+ /* notify MSC/SGSN that the subscriber data in VLR has been updated */
+ void (*subscr_update)(struct vlr_subscr *vsub);
+ /* notify MSC/SGSN that the given subscriber has been associated
+ * with this msc_conn_ref */
+ int (*subscr_assoc)(void *msc_conn_ref, struct vlr_subscr *vsub);
+ /* notify MSC that the given subscriber is no longer valid.
+ * If cancel_by_update, the MSC doesn't inform the MS/UE about it.
+ */
+ void (*subscr_inval)(void *msc_conn_ref, struct vlr_subscr *vsub,
+ enum gsm48_reject_value cause, bool cancel_by_update);
+};
+
+/* An instance of the VLR codebase */
+struct vlr_instance {
+ struct llist_head subscribers;
+ struct llist_head operations;
+ struct gsup_client_mux *gcm;
+ struct vlr_ops ops;
+ struct osmo_timer_list lu_expire_timer;
+ struct {
+ bool retrieve_imeisv_early;
+ bool retrieve_imeisv_ciphered;
+ bool assign_tmsi;
+ bool check_imei_rqd;
+ int auth_tuple_max_reuse_count;
+ bool auth_reuse_old_sets_on_error;
+ bool parq_retrieve_imsi;
+ bool is_ps;
+ uint8_t nri_bitlen;
+ struct osmo_nri_ranges *nri_ranges;
+ } cfg;
+ struct osmo_stat_item_group *statg;
+ struct rate_ctr_group *ctrg;
+ /* A free-form pointer for use by the caller */
+ void *user_ctx;
+};
+
+extern const struct value_string vlr_ciph_names[];
+static inline const char *vlr_ciph_name(enum vlr_ciph val)
+{
+ return get_value_string(vlr_ciph_names, val);
+}
+
+/* Location Updating request */
+struct osmo_fsm_inst *
+vlr_loc_update(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_lu_type lu_type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *old_lai,
+ const struct osmo_location_area_id *new_lai,
+ bool authentication_required,
+ bool is_ciphering_to_be_attempted,
+ bool is_ciphering_required,
+ uint8_t key_seq,
+ bool is_r99, bool is_utran,
+ bool assign_tmsi);
+
+void vlr_loc_update_cancel(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause fsm_cause,
+ uint8_t gsm48_cause);
+
+/* tell the VLR that the RAN connection is gone */
+int vlr_subscr_disconnected(struct vlr_subscr *vsub);
+bool vlr_subscr_expire(struct vlr_subscr *vsub);
+int vlr_subscr_rx_id_resp(struct vlr_subscr *vsub, const struct osmo_mobile_identity *mi);
+int vlr_subscr_rx_auth_resp(struct vlr_subscr *vsub, bool is_r99, bool is_utran,
+ const uint8_t *res, uint8_t res_len);
+int vlr_subscr_rx_auth_fail(struct vlr_subscr *vsub, const uint8_t *auts);
+int vlr_subscr_tx_auth_fail_rep(const struct vlr_subscr *vsub) __attribute__((warn_unused_result));
+void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, enum vlr_ciph_result_cause result);
+int vlr_subscr_rx_tmsi_reall_compl(struct vlr_subscr *vsub);
+int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub);
+
+struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops, bool is_ps);
+int vlr_start(struct vlr_instance *vlr, struct gsup_client_mux *gcm);
+int vlr_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg);
+
+static inline bool vlr_is_cs(struct vlr_instance *vlr) { return !vlr->cfg.is_ps; };
+static inline bool vlr_is_ps(struct vlr_instance *vlr) { return vlr->cfg.is_ps; };
+
+/* internal use only */
+
+void sub_pres_vlr_fsm_start(struct osmo_fsm_inst **fsm,
+ struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t term_event);
+struct osmo_fsm_inst *
+upd_hlr_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t parent_event);
+
+struct osmo_fsm_inst *
+lu_compl_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ void *msc_conn_ref,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure);
+
+
+const char *vlr_subscr_name(const struct vlr_subscr *vsub);
+const char *vlr_subscr_short_name(const struct vlr_subscr *vsub, unsigned int maxlen);
+const char *vlr_subscr_msisdn_or_name(const struct vlr_subscr *vsub);
+
+#define vlr_subscr_find_by_imsi(vlr, imsi, USE) \
+ _vlr_subscr_find_by_imsi(vlr, imsi, USE, __FILE__, __LINE__)
+#define vlr_subscr_find_or_create_by_imsi(vlr, imsi, USE, created) \
+ _vlr_subscr_find_or_create_by_imsi(vlr, imsi, USE, created, \
+ __FILE__, __LINE__)
+
+#define vlr_subscr_find_by_tmsi(vlr, tmsi, USE) \
+ _vlr_subscr_find_by_tmsi(vlr, tmsi, USE, __FILE__, __LINE__)
+#define vlr_subscr_find_or_create_by_tmsi(vlr, tmsi, USE, created) \
+ _vlr_subscr_find_or_create_by_tmsi(vlr, tmsi, USE, created, \
+ __FILE__, __LINE__)
+
+#define vlr_subscr_find_by_msisdn(vlr, msisdn, USE) \
+ _vlr_subscr_find_by_msisdn(vlr, msisdn, USE, __FILE__, __LINE__)
+
+#define vlr_subscr_find_by_mi(vlr, mi, USE) \
+ _vlr_subscr_find_by_mi(vlr, mi, USE, __FILE__, __LINE__)
+
+struct vlr_subscr *_vlr_subscr_find_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ const char *use,
+ const char *file, int line);
+struct vlr_subscr *_vlr_subscr_find_or_create_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ const char *use,
+ bool *created,
+ const char *file,
+ int line);
+
+struct vlr_subscr *_vlr_subscr_find_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ const char *use,
+ const char *file, int line);
+struct vlr_subscr *_vlr_subscr_find_or_create_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ const char *use,
+ bool *created,
+ const char *file,
+ int line);
+
+struct vlr_subscr *_vlr_subscr_find_by_msisdn(struct vlr_instance *vlr,
+ const char *msisdn,
+ const char *use,
+ const char *file, int line);
+
+struct vlr_subscr *_vlr_subscr_find_by_mi(struct vlr_instance *vlr,
+ const struct osmo_mobile_identity *mi,
+ const char *use,
+ const char *file, int line);
+
+#define vlr_subscr_get(VSUB, USE) vlr_subscr_get_src(VSUB, USE, __FILE__, __LINE__)
+#define vlr_subscr_put(VSUB, USE) vlr_subscr_put_src(VSUB, USE, __FILE__, __LINE__)
+
+#define vlr_subscr_get_src(VSUB, USE, SRCFILE, SRCLINE) \
+ OSMO_ASSERT((VSUB) && _osmo_use_count_get_put(&(VSUB)->use_count, USE, 1, SRCFILE, SRCLINE) == 0)
+#define vlr_subscr_put_src(VSUB, USE, SRCFILE, SRCLINE) \
+ OSMO_ASSERT((VSUB) && _osmo_use_count_get_put(&(VSUB)->use_count, USE, -1, SRCFILE, SRCLINE) == 0)
+
+void vlr_subscr_free(struct vlr_subscr *vsub);
+int vlr_subscr_alloc_tmsi(struct vlr_subscr *vsub);
+
+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);
+bool vlr_subscr_matches_msisdn(struct vlr_subscr *vsub, const char *msisdn);
+bool vlr_subscr_matches_imei(struct vlr_subscr *vsub, const char *imei);
+
+unsigned long vlr_timer_secs(struct vlr_instance *vlr, int cs_timer, int ps_timer);
+
+/* vlr_tdefs is pointing to msc_tdefs_vlr or sgsn_tdefs_vlr depending on the domain*/
+extern struct osmo_tdef *vlr_tdefs;
+
+int vlr_subscr_changed(struct vlr_subscr *vsub);
+int vlr_subscr_purge(struct vlr_subscr *vsub) __attribute__((warn_unused_result));
+void vlr_subscr_cancel_attach_fsm(struct vlr_subscr *vsub,
+ enum osmo_fsm_term_cause fsm_cause,
+ enum gsm48_reject_value gsm48_cause);
+
+void vlr_subscr_enable_expire_lu(struct vlr_subscr *vsub);
+
+/* Process Access Request FSM */
+
+enum proc_arq_vlr_event {
+ PR_ARQ_E_START,
+ PR_ARQ_E_ID_IMSI,
+ PR_ARQ_E_AUTH_RES,
+ PR_ARQ_E_AUTH_NO_INFO,
+ PR_ARQ_E_AUTH_FAILURE,
+ PR_ARQ_E_CIPH_RES,
+ PR_ARQ_E_UPD_LOC_RES,
+ PR_ARQ_E_TRACE_RES,
+ PR_ARQ_E_IMEI_RES,
+ PR_ARQ_E_PRES_RES,
+ PR_ARQ_E_TMSI_ACK,
+};
+
+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 */
+};
+
+/* Process Access Request (CM SERV REQ / PAGING RESP) */
+void
+vlr_proc_acc_req(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_parq_type type, enum osmo_cm_service_type cm_service_type,
+ const struct osmo_mobile_identity *mi,
+ const struct osmo_location_area_id *lai,
+ bool authentication_required,
+ bool is_ciphering_to_be_attempted,
+ bool is_ciphering_required,
+ uint8_t key_seq,
+ bool is_r99, bool is_utran);
+
+void vlr_parq_cancel(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause fsm_cause,
+ enum gsm48_reject_value gsm48_cause);
+
+int vlr_set_ciph_mode(struct vlr_instance *vlr,
+ struct osmo_fsm_inst *fi,
+ void *msc_conn_ref,
+ bool umts_aka,
+ bool retrieve_imeisv);
+
+bool vlr_use_umts_aka(struct osmo_auth_vector *vec, bool is_r99);
+
+void log_set_filter_vlr_subscr(struct log_target *target,
+ struct vlr_subscr *vlr_subscr);
+
+enum gsm48_reject_value vlr_gmm_cause_to_reject_cause(enum gsm48_gmm_cause gmm_cause);
+enum gsm48_reject_value vlr_gmm_cause_to_reject_cause_domain(enum gsm48_gmm_cause gmm_cause, bool is_cs);
+enum gsm48_reject_value vlr_reject_causes_cs(enum gsm48_reject_value reject_cause);
+enum gsm48_reject_value vlr_reject_causes_ps(enum gsm48_reject_value reject_cause);
+
+/* logging */
+enum osmo_vlr_cat {
+ OSMO_VLR_LOGC_VLR,
+ OSMO_VLR_LOGC_SGS,
+ _OSMO_VLR_LOGC_MAX,
+};
+
+void osmo_vlr_set_log_cat(enum osmo_vlr_cat logc, int logc_num);
diff --git a/include/osmocom/vlr/vlr_sgs.h b/include/osmocom/vlr/vlr_sgs.h
new file mode 100644
index 0000000..7193fa5
--- /dev/null
+++ b/include/osmocom/vlr/vlr_sgs.h
@@ -0,0 +1,111 @@
+/* (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
+
+#include <osmocom/gsm/gsm29118.h>
+
+enum vlr_lu_type;
+struct vlr_subscr;
+struct vlr_instance;
+
+#define VSUB_USE_SGS_LU "SGs-lu"
+#define VSUB_USE_SGS_PAGING_REQ "SGs-paging-req"
+
+/* See also 3GPP TS 29.118, chapter 4.2.2 States at the VLR */
+enum sgs_ue_fsm_state {
+ SGS_UE_ST_NULL,
+ SGS_UE_ST_LA_UPD_PRES,
+ SGS_UE_ST_ASSOCIATED,
+};
+
+enum vlr_sgs_state_tmr {
+ /* Started when sending the SGsAP-PAGING-REQUEST, implemented in vlr_sgs.c */
+ SGS_STATE_TS5,
+ /* TMSI reallocation, 5.2.3.5, implemented by fsm in vlr_sgs_fsm.c */
+ SGS_STATE_TS6_2,
+ /* Started when SGsAP-ALERT-REQUEST is sent 5.3.2.1, not implemented yet */
+ SGS_STATE_TS7,
+ /* Reset ack timeout, implemnted in sgs_iface.c */
+ SGS_STATE_TS11,
+ /* Started when SGsAP-SERVICE-REQUEST is received 5.15.1, not implemented yet */
+ SGS_STATE_TS14,
+ /* Started when SGsAP-MO-CSFB-INDICATION is received 5.16.3 (UE fallback, not implemented yet) */
+ SGS_STATE_TS15,
+ _NUM_SGS_STATE_TIMERS
+};
+
+enum vlr_sgs_state_ctr {
+ /* Alert request retransmit count */
+ SGS_STATE_NS7,
+ /* Reset repeat count */
+ SGS_STATE_NS11,
+ _NUM_SGS_STATE_COUNTERS
+};
+
+extern const struct value_string sgs_state_timer_names[];
+static inline const char *vlr_sgs_state_timer_name(enum vlr_sgs_state_tmr Ts)
+{
+ return get_value_string(sgs_state_timer_names, Ts);
+}
+
+extern const struct value_string sgs_state_counter_names[];
+static inline const char *vlr_sgs_state_counter_name(enum vlr_sgs_state_ctr Ns)
+{
+ return get_value_string(sgs_state_counter_names, Ns);
+}
+
+/* This callback function is called when an SGs location update is complete */
+struct sgs_lu_response {
+ bool accepted;
+ bool error;
+ struct vlr_subscr *vsub;
+};
+typedef void (*vlr_sgs_lu_response_cb_t) (struct sgs_lu_response *response);
+
+/* This callback function is called in cases where a paging request is required
+ * after the LU is completed or when a paging expired */
+typedef int (*vlr_sgs_lu_paging_cb_t) (struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind);
+
+/* This callback function is called to send the MM info to the UE. */
+typedef void (*vlr_sgs_lu_mminfo_cb_t) (struct vlr_subscr *vsub);
+
+/* Configuration parameters for the SGs FSM */
+struct vlr_sgs_cfg {
+ unsigned int timer[_NUM_SGS_STATE_TIMERS];
+ unsigned int counter[_NUM_SGS_STATE_COUNTERS];
+};
+
+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_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);
+void vlr_sgs_imsi_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_imsi_det_noneps_type type);
+void vlr_sgs_eps_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_imsi_det_eps_type type);
+void vlr_sgs_tmsi_reall_compl(struct vlr_instance *vlr, const char *imsi);
+void vlr_sgs_pag_rej(struct vlr_instance *vlr, const char *imsi, enum sgsap_sgs_cause cause);
+void vlr_sgs_pag_ack(struct vlr_instance *vlr, const char *imsi);
+void vlr_sgs_ue_unr(struct vlr_instance *vlr, const char *imsi, enum sgsap_sgs_cause cause);
+void vlr_sgs_pag(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind);
+bool vlr_sgs_pag_pend(struct vlr_subscr *vsub);
diff --git a/src/Makefile.am b/src/Makefile.am
index e389b7f..6f8eaa5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,5 @@
SUBDIRS = \
+ libvlr \
gprs \
sgsn \
gtphub \
diff --git a/src/libvlr/Makefile.am b/src/libvlr/Makefile.am
new file mode 100644
index 0000000..49bf573
--- /dev/null
+++ b/src/libvlr/Makefile.am
@@ -0,0 +1,32 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS= \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(LIBOSMOGSUPCLIENT_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMORANAP_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+noinst_HEADERS = \
+ vlr_access_req_fsm.h \
+ vlr_auth_fsm.h \
+ vlr_core.h \
+ vlr_lu_fsm.h \
+ vlr_sgs_fsm.h \
+ $(NULL)
+
+noinst_LIBRARIES = libvlr.a
+
+libvlr_a_SOURCES = \
+ vlr.c \
+ 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
new file mode 100644
index 0000000..2b56a8b
--- /dev/null
+++ b/src/libvlr/vlr.c
@@ -0,0 +1,1754 @@
+/* Osmocom Visitor Location Register (VLR) code base */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsm23236.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include <osmocom/vlr/vlr_sgs.h>
+#include <osmocom/vlr/vlr.h>
+#include <osmocom/gsupclient/gsup_client_mux.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+#include "vlr_core.h"
+#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
+
+enum vlr_stat_item_idx {
+ VLR_STAT_SUBSCRIBER_COUNT,
+ VLR_STAT_PDP_COUNT,
+};
+
+static const struct osmo_stat_item_desc vlr_stat_item_desc[] = {
+ [VLR_STAT_SUBSCRIBER_COUNT] = { "subscribers",
+ "Number of subscribers present in VLR" },
+ [VLR_STAT_PDP_COUNT] = { "pdp",
+ "Number of PDP records present in VLR" },
+};
+
+static const struct osmo_stat_item_group_desc vlr_statg_desc = {
+ "vlr",
+ "visitor location register",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(vlr_stat_item_desc),
+ vlr_stat_item_desc,
+};
+
+enum vlr_rate_ctr_idx {
+ VLR_CTR_GSUP_RX_UNKNOWN_IMSI,
+ VLR_CTR_GSUP_RX_PURGE_NO_SUBSCR,
+ VLR_CTR_GSUP_RX_TUPLES,
+ VLR_CTR_GSUP_RX_UL_RES,
+ VLR_CTR_GSUP_RX_UL_ERR,
+ VLR_CTR_GSUP_RX_SAI_RES,
+ VLR_CTR_GSUP_RX_SAI_ERR,
+ VLR_CTR_GSUP_RX_ISD_REQ,
+ VLR_CTR_GSUP_RX_CANCEL_REQ,
+ VLR_CTR_GSUP_RX_CHECK_IMEI_RES,
+ VLR_CTR_GSUP_RX_CHECK_IMEI_ERR,
+ VLR_CTR_GSUP_RX_PURGE_MS_RES,
+ VLR_CTR_GSUP_RX_PURGE_MS_ERR,
+ VLR_CTR_GSUP_RX_DELETE_DATA_REQ,
+ VLR_CTR_GSUP_RX_UNKNOWN,
+
+ VLR_CTR_GSUP_TX_UL_REQ,
+ VLR_CTR_GSUP_TX_ISD_RES,
+ VLR_CTR_GSUP_TX_SAI_REQ,
+ VLR_CTR_GSUP_TX_PURGE_MS_REQ,
+ VLR_CTR_GSUP_TX_CHECK_IMEI_REQ,
+ VLR_CTR_GSUP_TX_AUTH_FAIL_REP,
+ VLR_CTR_GSUP_TX_CANCEL_RES,
+
+ VLR_CTR_DETACH_BY_REQ,
+ VLR_CTR_DETACH_BY_CANCEL,
+ VLR_CTR_DETACH_BY_T3212,
+};
+
+static const struct rate_ctr_desc vlr_ctr_desc[] = {
+ [VLR_CTR_GSUP_RX_UNKNOWN_IMSI] = { "gsup:rx:unknown_imsi",
+ "Received GSUP messages for unknown IMSI" },
+ [VLR_CTR_GSUP_RX_PURGE_NO_SUBSCR] = { "gsup:rx:purge_no_subscr",
+ "Received GSUP purge for unknown subscriber" },
+ [VLR_CTR_GSUP_RX_TUPLES] = { "gsup:rx:auth_tuples",
+ "Received GSUP authentication tuples" },
+ [VLR_CTR_GSUP_RX_UL_RES] = { "gsup:rx:upd_loc:res",
+ "Received GSUP Update Location Result messages" },
+ [VLR_CTR_GSUP_RX_UL_ERR] = { "gsup:rx:upd_loc:err",
+ "Received GSUP Update Location Error messages" },
+ [VLR_CTR_GSUP_RX_SAI_RES] = { "gsup:rx:send_auth_info:res",
+ "Received GSUP Send Auth Info Result messages" },
+ [VLR_CTR_GSUP_RX_SAI_ERR] = { "gsup:rx:send_auth_info:err",
+ "Received GSUP Send Auth Info Error messages" },
+ [VLR_CTR_GSUP_RX_ISD_REQ] = { "gsup:rx:ins_sub_data:req",
+ "Received GSUP Insert Subscriber Data Request messages" },
+ [VLR_CTR_GSUP_RX_CANCEL_REQ] = { "gsup:rx:cancel:req",
+ "Received GSUP Cancel Subscriber messages" },
+ [VLR_CTR_GSUP_RX_CHECK_IMEI_RES] = { "gsup:rx:check_imei:res",
+ "Received GSUP Check IMEI Result messages" },
+ [VLR_CTR_GSUP_RX_CHECK_IMEI_ERR] = { "gsup:rx:check_imei:err",
+ "Received GSUP Check IMEI Error messages" },
+ [VLR_CTR_GSUP_RX_PURGE_MS_RES] = { "gsup:rx:purge_ms:res",
+ "Received GSUP Purge MS Result messages" },
+ [VLR_CTR_GSUP_RX_PURGE_MS_ERR] = { "gsup:rx:purge_ms:err",
+ "Received GSUP Purge MS Error messages" },
+ [VLR_CTR_GSUP_RX_DELETE_DATA_REQ] = { "gsup:rx:del_sub_data:req",
+ "Received GSUP Delete Subscriber Data Request messages" },
+ [VLR_CTR_GSUP_RX_UNKNOWN] = { "gsup:rx:unknown_msgtype",
+ "Received GSUP message of unknown type" },
+
+ [VLR_CTR_GSUP_TX_UL_REQ] = { "gsup:tx:upd_loc:req",
+ "Transmitted GSUP Update Location Request messages" },
+ [VLR_CTR_GSUP_TX_ISD_RES] = { "gsup:tx:ins_sub_data:res",
+ "Transmitted GSUP Insert Subscriber Data Result messages" },
+ [VLR_CTR_GSUP_TX_SAI_REQ] = { "gsup:tx:send_auth_info:res",
+ "Transmitted GSUP Send Auth Info Request messages" },
+ [VLR_CTR_GSUP_TX_PURGE_MS_REQ] = { "gsup:tx:purge_ms:req",
+ "Transmitted GSUP Purge MS Request messages" },
+ [VLR_CTR_GSUP_TX_CHECK_IMEI_REQ] = { "gsup:tx:check_imei:req",
+ "Transmitted GSUP Check IMEI Request messages" },
+ [VLR_CTR_GSUP_TX_AUTH_FAIL_REP] = { "gsup:tx:auth_fail:rep",
+ "Transmitted GSUP Auth Fail Report messages" },
+ [VLR_CTR_GSUP_TX_CANCEL_RES] = { "gsup:tx:cancel:res",
+ "Transmitted GSUP Cancel Result messages" },
+
+ [VLR_CTR_DETACH_BY_REQ] = { "detach:imsi_det_req",
+ "VLR Subscriber Detach by IMSI DETACH REQ" },
+ [VLR_CTR_DETACH_BY_CANCEL] = { "detach:gsup_cancel_req",
+ "VLR Subscriber Detach by GSUP CANCEL REQ" },
+ [VLR_CTR_DETACH_BY_T3212] = { "detach:t3212_timeout",
+ "VLR Subscriber Detach by T3212 timeout" },
+};
+
+static const struct rate_ctr_group_desc vlr_ctrg_desc = {
+ "vlr",
+ "visitor location register",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(vlr_ctr_desc),
+ vlr_ctr_desc,
+};
+
+
+#define vlr_rate_ctr_inc(vlr, idx) \
+ rate_ctr_inc(rate_ctr_group_get_ctr((vlr)->ctrg, idx))
+#define vlr_rate_ctr_add(vlr, idx, val) \
+ rate_ctr_add(rate_ctr_group_get_ctr((vlr)->ctrg, idx), val)
+
+#define vlr_stat_item_inc(vlr, idx) \
+ osmo_stat_item_inc(osmo_stat_item_group_get_item((vlr)->statg, idx), 1)
+#define vlr_stat_item_dec(vlr, idx) \
+ osmo_stat_item_dec(osmo_stat_item_group_get_item((vlr)->statg, idx), 1)
+#define vlr_stat_item_set(vlr, idx, val) \
+ osmo_stat_item_set(osmo_stat_item_group_get_item((vlr)->statg, idx), val)
+
+/***********************************************************************
+ * Convenience functions
+ ***********************************************************************/
+
+static int vlr_subscr_detach(struct vlr_subscr *vsub);
+
+const struct value_string vlr_ciph_names[] = {
+ OSMO_VALUE_STRING(VLR_CIPH_NONE),
+ OSMO_VALUE_STRING(VLR_CIPH_A5_1),
+ OSMO_VALUE_STRING(VLR_CIPH_A5_2),
+ OSMO_VALUE_STRING(VLR_CIPH_A5_3),
+ { 0, NULL }
+};
+
+/* 3GPP TS 24.008, table 11.2 Mobility management timers (network-side) */
+struct osmo_tdef msc_tdefs_vlr[] = {
+ { .T = 3212, .default_val = 60, .unit = OSMO_TDEF_M, .desc = "Subscriber expiration timeout" },
+ { .T = 3250, .default_val = 12, .desc = "TMSI Reallocation procedure" },
+ { .T = 3260, .default_val = 12, .desc = "Authentication procedure" },
+ { .T = 3270, .default_val = 12, .desc = "Identification procedure" },
+ { /* terminator */ }
+};
+
+/* 3GPP TS 24.008, table 11.2 Mobility management timers (network-side) */
+struct osmo_tdef sgsn_tdefs_vlr[] = {
+ { .T = 3312, .default_val = 60, .unit = OSMO_TDEF_M, .desc = "Subscriber expiration timeout" },
+ { .T = 3350, .default_val = 6, .desc = "Attach/RAU Complete Reallocation procedure" },
+ { .T = 3360, .default_val = 6, .desc = "Authentication procedure" },
+ { .T = 3370, .default_val = 6, .desc = "Identification procedure" },
+ { /* terminator */ }
+};
+
+struct osmo_tdef *vlr_tdefs;
+
+/* This is just a wrapper around the osmo_tdef API.
+ * TODO: we should start using osmo_tdef_fsm_inst_state_chg() */
+unsigned long vlr_timer_secs(struct vlr_instance *vlr, int cs_timer, int ps_timer)
+{
+ /* NOTE: since we usually do not need more than one instance of the VLR,
+ * and since libosmocore's osmo_tdef API does not (yet) support dynamic
+ * configuration, we always use the global instance of msc_tdefs_vlr. */
+ if (vlr_is_cs(vlr))
+ return osmo_tdef_get(vlr_tdefs, cs_timer, OSMO_TDEF_S, 0);
+ else
+ return osmo_tdef_get(vlr_tdefs, ps_timer, OSMO_TDEF_S, 0);
+}
+
+/* return static buffer with printable name of VLR subscriber */
+const char *vlr_subscr_name(const struct vlr_subscr *vsub)
+{
+ static char buf[128];
+ struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
+ bool present = false;
+ if (!vsub)
+ return "unknown";
+ if (vsub->imsi[0]) {
+ OSMO_STRBUF_PRINTF(sb, "IMSI-%s", vsub->imsi);
+ present = true;
+ }
+ if (vsub->msisdn[0]) {
+ OSMO_STRBUF_PRINTF(sb, "%sMSISDN-%s", present? ":" : "", vsub->msisdn);
+ present = true;
+ }
+ if (vsub->tmsi != GSM_RESERVED_TMSI) {
+ OSMO_STRBUF_PRINTF(sb, "%sTMSI-0x%08X", present? ":" : "", vsub->tmsi);
+ present = true;
+ }
+ if (vsub->tmsi_new != GSM_RESERVED_TMSI) {
+ OSMO_STRBUF_PRINTF(sb, "%sTMSInew-0x%08X", present? ":" : "", vsub->tmsi_new);
+ present = true;
+ }
+ if (!present)
+ return "unknown";
+
+ return buf;
+}
+
+const char *vlr_subscr_short_name(const struct vlr_subscr *vsub, unsigned int maxlen)
+{
+ /* cast away the const so we can shorten the string within the static buffer */
+ char *name = (char*)vlr_subscr_name(vsub);
+ size_t len = strlen(name);
+ if (maxlen < 2)
+ return "-";
+ if (len > maxlen)
+ strcpy(name + maxlen - 2, "..");
+ return name;
+}
+
+const char *vlr_subscr_msisdn_or_name(const struct vlr_subscr *vsub)
+{
+ if (!vsub || !vsub->msisdn[0])
+ return vlr_subscr_name(vsub);
+ return vsub->msisdn;
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ const char *use,
+ const char *file, int line)
+{
+ struct vlr_subscr *vsub;
+
+ if (!imsi || !*imsi)
+ return NULL;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vlr_subscr_matches_imsi(vsub, imsi)) {
+ if (use)
+ vlr_subscr_get_src(vsub, use, file, line);
+ return vsub;
+ }
+ }
+ return NULL;
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ const char *use,
+ const char *file, int line)
+{
+ struct vlr_subscr *vsub;
+
+ if (tmsi == GSM_RESERVED_TMSI)
+ return NULL;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vlr_subscr_matches_tmsi(vsub, tmsi)) {
+ vlr_subscr_get_src(vsub, use, file, line);
+ return vsub;
+ }
+ }
+ return NULL;
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_msisdn(struct vlr_instance *vlr,
+ const char *msisdn,
+ const char *use,
+ const char *file, int line)
+{
+ struct vlr_subscr *vsub;
+
+ if (!msisdn || !*msisdn)
+ return NULL;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vlr_subscr_matches_msisdn(vsub, msisdn)) {
+ vlr_subscr_get_src(vsub, use, file, line);
+ return vsub;
+ }
+ }
+ return NULL;
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_mi(struct vlr_instance *vlr,
+ const struct osmo_mobile_identity *mi,
+ const char *use,
+ const char *file, int line)
+{
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ return _vlr_subscr_find_by_imsi(vlr, mi->imsi, use, file, line);
+ case GSM_MI_TYPE_TMSI:
+ return _vlr_subscr_find_by_tmsi(vlr, mi->tmsi, use, file, line);
+ default:
+ return NULL;
+ }
+}
+
+/* Transmit GSUP message for subscriber to HLR, using IMSI from subscriber */
+static int vlr_subscr_tx_gsup_message(const struct vlr_subscr *vsub,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct vlr_instance *vlr = vsub->vlr;
+
+ if (strlen(gsup_msg->imsi) == 0)
+ OSMO_STRLCPY_ARRAY(gsup_msg->imsi, vsub->imsi);
+
+ gsup_msg->message_class = OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT;
+
+ return gsup_client_mux_tx(vlr->gcm, gsup_msg);
+}
+
+static int vlr_subscr_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
+{
+ struct vlr_subscr *vsub = e->use_count->talloc_object;
+ char buf[128];
+ int32_t total;
+ int level;
+
+ if (!e->use)
+ return -EINVAL;
+
+ total = osmo_use_count_total(&vsub->use_count);
+
+ if (total == 0
+ || (total == 1 && old_use_count == 0 && e->count == 1))
+ level = LOGL_INFO;
+ else
+ level = LOGL_DEBUG;
+
+ LOGPSRC(g_vlr_log_cat[OSMO_VLR_LOGC_VLR], level, file, line, "VLR subscr %s %s %s: now used by %s\n",
+ vlr_subscr_name(vsub), (e->count - old_use_count) > 0? "+" : "-", e->use,
+ osmo_use_count_name_buf(buf, sizeof(buf), e->use_count));
+
+ if (e->count < 0)
+ return -ERANGE;
+
+ vsub->max_total_use_count = OSMO_MAX(vsub->max_total_use_count, total);
+
+ if (total <= 0)
+ vlr_subscr_free(vsub);
+ return 0;
+}
+
+/* Allocate a new subscriber and insert it into list */
+static struct vlr_subscr *_vlr_subscr_alloc(struct vlr_instance *vlr)
+{
+ struct vlr_subscr *vsub;
+ int i;
+
+ vsub = talloc_zero(vlr, struct vlr_subscr);
+ *vsub = (struct vlr_subscr){
+ .vlr = vlr,
+ .tmsi = GSM_RESERVED_TMSI,
+ .tmsi_new = GSM_RESERVED_TMSI,
+ .use_count = (struct osmo_use_count){
+ .talloc_object = vsub,
+ .use_cb = vlr_subscr_use_cb,
+ },
+ .expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION,
+ };
+ osmo_use_count_make_static_entries(&vsub->use_count, vsub->use_count_buf, ARRAY_SIZE(vsub->use_count_buf));
+
+ for (i = 0; i < ARRAY_SIZE(vsub->auth_tuples); i++)
+ vsub->auth_tuples[i].key_seq = VLR_KEY_SEQ_INVAL;
+
+ 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);
+ vlr_stat_item_inc(vlr, VLR_STAT_SUBSCRIBER_COUNT);
+ return vsub;
+}
+
+/* Send a GSUP Purge MS request.
+ * TODO: this should be sent to the *previous* VLR when this VLR is "taking"
+ * this subscriber, not to the HLR? */
+int vlr_subscr_purge(struct vlr_subscr *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_PURGE_MS_REQ);
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST;
+
+ /* provide HLR number in case we know it */
+ if (vsub->hlr.len) {
+ gsup_msg.hlr_enc_len = vsub->hlr.len;
+ gsup_msg.hlr_enc = vsub->hlr.buf;
+ }
+
+ gsup_msg.cn_domain = vlr_is_cs(vsub->vlr) ? OSMO_GSUP_CN_DOMAIN_CS : OSMO_GSUP_CN_DOMAIN_PS;
+
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+}
+
+void vlr_subscr_cancel_attach_fsm(struct vlr_subscr *vsub,
+ enum osmo_fsm_term_cause fsm_cause,
+ enum gsm48_reject_value gsm48_cause)
+{
+ if (!vsub)
+ return;
+
+ vlr_subscr_get(vsub, __func__);
+ if (vsub->lu_fsm)
+ vlr_loc_update_cancel(vsub->lu_fsm, fsm_cause, gsm48_cause);
+ if (vsub->proc_arq_fsm)
+ vlr_parq_cancel(vsub->proc_arq_fsm, fsm_cause, gsm48_cause);
+ vlr_subscr_put(vsub, __func__);
+}
+
+/* Call vlr_subscr_cancel(), then completely drop the entry from the VLR */
+void vlr_subscr_free(struct vlr_subscr *vsub)
+{
+ llist_del(&vsub->list);
+ vlr_stat_item_dec(vsub->vlr, VLR_STAT_SUBSCRIBER_COUNT);
+ LOGVSUBP(LOGL_DEBUG, vsub, "freeing VLR subscr (max total use count was %d)\n",
+ vsub->max_total_use_count);
+
+ /* 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);
+}
+
+/* Generate a new TMSI and store in vsub->tmsi_new.
+ * Search all known subscribers to ensure that the TMSI is unique. */
+int vlr_subscr_alloc_tmsi(struct vlr_subscr *vsub)
+{
+ struct vlr_instance *vlr = vsub->vlr;
+ uint32_t tmsi;
+ int tried, rc;
+ struct vlr_subscr *other_vsub;
+
+ for (tried = 0; tried < 100; tried++) {
+ rc = osmo_get_rand_id((uint8_t *) &tmsi, sizeof(tmsi));
+ if (rc < 0) {
+ LOGVLR(LOGL_ERROR, "osmo_get_rand_id() failed: %s\n", strerror(-rc));
+ return rc;
+ }
+
+ if (!llist_empty(&vlr->cfg.nri_ranges->entries)) {
+ int16_t nri_v;
+ osmo_tmsi_nri_v_limit_by_ranges(&tmsi, vlr->cfg.nri_ranges, vlr->cfg.nri_bitlen);
+ osmo_tmsi_nri_v_get(&nri_v, tmsi, vlr->cfg.nri_bitlen);
+ LOGVLR(LOGL_DEBUG, "New NRI from range [%s] = 0x%x --> TMSI 0x%08x\n",
+ osmo_nri_ranges_to_str_c(OTC_SELECT, vlr->cfg.nri_ranges), nri_v, tmsi);
+ }
+
+ /* throw the dice again, if the TSMI doesn't fit */
+ if (tmsi == GSM_RESERVED_TMSI)
+ continue;
+
+ /* Section 2.4 of 23.003: MSC has two MSB 00/01/10, SGSN 11 */
+ if (vlr->cfg.is_ps) {
+ /* SGSN */
+ tmsi |= GSM23003_TMSI_SGSN_MASK;
+ } else {
+ /* MSC */
+ if ((tmsi & GSM23003_TMSI_SGSN_MASK) == GSM23003_TMSI_SGSN_MASK)
+ tmsi &= ~GSM23003_TMSI_SGSN_MASK;
+ }
+
+ /* If this TMSI is already in use, try another one. */
+ if ((other_vsub = vlr_subscr_find_by_tmsi(vlr, tmsi, __func__))) {
+ vlr_subscr_put(other_vsub, __func__);
+ continue;
+ }
+
+ vsub->tmsi_new = tmsi;
+ vsub->vlr->ops.subscr_update(vsub);
+ return 0;
+ }
+
+ LOGVSUBP(LOGL_ERROR, vsub, "Unable to generate valid TMSI"
+ " after %d tries\n", tried);
+ return -1;
+}
+
+/* Find subscriber by IMSI, or create new subscriber if not found.
+ * \param[in] vlr VLR instance.
+ * \param[in] imsi IMSI string.
+ * \param[out] created if non-NULL, returns whether a new entry was created. */
+struct vlr_subscr *_vlr_subscr_find_or_create_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ const char *use,
+ bool *created,
+ const char *file,
+ int line)
+{
+ struct vlr_subscr *vsub;
+ vsub = _vlr_subscr_find_by_imsi(vlr, imsi, use, file, line);
+ if (vsub) {
+ if (created)
+ *created = false;
+ return vsub;
+ }
+
+ vsub = _vlr_subscr_alloc(vlr);
+ if (!vsub)
+ return NULL;
+ vlr_subscr_get_src(vsub, use, file, line);
+ vlr_subscr_set_imsi(vsub, imsi);
+ LOGVLR(LOGL_INFO, "New subscr, IMSI: %s\n", vsub->imsi);
+ if (created)
+ *created = true;
+ return vsub;
+}
+
+/* Find subscriber by TMSI, or create new subscriber if not found.
+ * \param[in] vlr VLR instance.
+ * \param[in] tmsi TMSI.
+ * \param[out] created if non-NULL, returns whether a new entry was created. */
+struct vlr_subscr *_vlr_subscr_find_or_create_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ const char *use,
+ bool *created,
+ const char *file,
+ int line)
+{
+ struct vlr_subscr *vsub;
+ vsub = _vlr_subscr_find_by_tmsi(vlr, tmsi, use, file, line);
+ if (vsub) {
+ if (created)
+ *created = false;
+ return vsub;
+ }
+
+ vsub = _vlr_subscr_alloc(vlr);
+ if (!vsub)
+ return NULL;
+ vlr_subscr_get_src(vsub, use, file, line);
+ vsub->tmsi = tmsi;
+ LOGVLR(LOGL_INFO, "New subscr, TMSI: 0x%08x\n", vsub->tmsi);
+ if (created)
+ *created = true;
+ return vsub;
+}
+
+static void dedup_vsub(struct vlr_subscr *exists, struct vlr_subscr *vsub)
+{
+ struct vlr_instance *vlr = exists->vlr;
+ int i;
+ int j;
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "There is an existing subscriber for IMSI %s used by %s, replacing with this VLR subscr, used by %s\n",
+ exists->imsi, osmo_use_count_to_str_c(OTC_SELECT, &exists->use_count),
+ osmo_use_count_to_str_c(OTC_SELECT, &vsub->use_count));
+
+ if (!vsub->msisdn[0])
+ OSMO_STRLCPY_ARRAY(vsub->msisdn, exists->msisdn);
+ if (!vsub->name[0])
+ OSMO_STRLCPY_ARRAY(vsub->name, exists->name);
+ /* Copy valid auth tuples we may already have, to reduce the need to ask for new ones from the HLR */
+ for (i = 0; i < ARRAY_SIZE(exists->auth_tuples); i++) {
+ if (exists->auth_tuples[i].key_seq == VLR_KEY_SEQ_INVAL)
+ continue;
+ for (j = 0; j < ARRAY_SIZE(vsub->auth_tuples); j++) {
+ if (vsub->auth_tuples[j].key_seq != VLR_KEY_SEQ_INVAL)
+ continue;
+ vsub->auth_tuples[j] = exists->auth_tuples[i];
+ }
+ }
+
+ if (exists->msc_conn_ref)
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "There is an existing VLR entry for this same subscriber with an active connection."
+ " That should not be possible. Discarding old subscriber entry %s.\n",
+ exists->imsi);
+
+ if (vlr->ops.subscr_inval)
+ vlr->ops.subscr_inval(exists->msc_conn_ref, exists, 0, true);
+ vlr_subscr_free(exists);
+}
+
+void vlr_subscr_set_imsi(struct vlr_subscr *vsub, const char *imsi)
+{
+ struct vlr_subscr *exists;
+ if (!vsub)
+ return;
+
+ /* If the same IMSI is already set, nothing changes. */
+ if (!strcmp(vsub->imsi, imsi))
+ return;
+
+ /* We've just learned about this new IMSI, our primary key in the VLR. make sure to invalidate any prior VLR
+ * entries for this IMSI. */
+ exists = vlr_subscr_find_by_imsi(vsub->vlr, imsi, NULL);
+
+ if (exists)
+ dedup_vsub(exists, vsub);
+
+ /* Set the IMSI on the new subscriber, here. */
+ if (OSMO_STRLCPY_ARRAY(vsub->imsi, imsi) >= sizeof(vsub->imsi)) {
+ LOGVLR(LOGL_NOTICE, "IMSI was truncated: full IMSI=%s, truncated IMSI=%s\n",
+ imsi, vsub->imsi);
+ /* XXX Set truncated IMSI anyway, we currently cannot return an error from here. */
+ }
+
+ vsub->id = atoll(vsub->imsi);
+ LOGVLR(LOGL_DEBUG, "set IMSI on subscriber; IMSI=%s id=%llu\n",
+ vsub->imsi, vsub->id);
+}
+
+void vlr_subscr_set_imei(struct vlr_subscr *vsub, const char *imei)
+{
+ if (!vsub)
+ return;
+ OSMO_STRLCPY_ARRAY(vsub->imei, imei);
+ LOGVLR(LOGL_DEBUG, "set IMEI on subscriber; IMSI=%s IMEI=%s\n",
+ vsub->imsi, vsub->imei);
+}
+
+void vlr_subscr_set_imeisv(struct vlr_subscr *vsub, const char *imeisv)
+{
+ if (!vsub)
+ return;
+ OSMO_STRLCPY_ARRAY(vsub->imeisv, imeisv);
+ LOGVLR(LOGL_DEBUG, "set IMEISV on subscriber; IMSI=%s IMEISV=%s\n",
+ vsub->imsi, vsub->imeisv);
+
+ /* Copy IMEISV to IMEI (additional SV digits get cut off) */
+ vlr_subscr_set_imei(vsub, imeisv);
+}
+
+/* Safely copy the given MSISDN string to vsub->msisdn */
+void vlr_subscr_set_msisdn(struct vlr_subscr *vsub, const char *msisdn)
+{
+ if (!vsub)
+ return;
+ OSMO_STRLCPY_ARRAY(vsub->msisdn, msisdn);
+ LOGVLR(LOGL_DEBUG, "set MSISDN on subscriber; IMSI=%s MSISDN=%s\n",
+ 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;
+ }
+ LOGVLR(LOGL_DEBUG, "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);
+}
+
+bool vlr_subscr_matches_tmsi(struct vlr_subscr *vsub, uint32_t tmsi)
+{
+ return vsub && tmsi != GSM_RESERVED_TMSI
+ && (vsub->tmsi == tmsi || vsub->tmsi_new == tmsi);
+}
+
+bool vlr_subscr_matches_msisdn(struct vlr_subscr *vsub, const char *msisdn)
+{
+ return vsub && msisdn && vsub->msisdn[0]
+ && !strcmp(vsub->msisdn, msisdn);
+}
+
+bool vlr_subscr_matches_imei(struct vlr_subscr *vsub, const char *imei)
+{
+ return vsub && imei && vsub->imei[0]
+ && !strcmp(vsub->imei, imei);
+}
+
+/* Send updated subscriber information to HLR */
+int vlr_subscr_changed(struct vlr_subscr *vsub)
+{
+ /* FIXME */
+ LOGVLR(LOGL_ERROR, "Not implemented: %s\n", __func__);
+ return 0;
+}
+
+void vlr_subscr_enable_expire_lu(struct vlr_subscr *vsub)
+{
+ struct timespec now;
+
+ /* Mark the subscriber as inactive if it stopped to do periodical location updates. */
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
+ vsub->expire_lu = now.tv_sec + vlr_timer_secs(vsub->vlr, 3212, 3312);
+ } else {
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "Could not enable Location Update expiry: unable to read current time\n");
+ /* Disable LU expiry for this subscriber. This subscriber will only be freed after an explicit IMSI detach. */
+ vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION;
+ }
+}
+
+void vlr_subscr_expire_lu(void *data)
+{
+ struct vlr_instance *vlr = data;
+ struct vlr_subscr *vsub, *vsub_tmp;
+ struct timespec now;
+
+ /* Periodic location update might be disabled from the VTY,
+ * so we shall not expire subscribers until explicit IMSI Detach. */
+ if (!vlr_timer_secs(vlr, 3212, 3312))
+ goto done;
+
+ if (llist_empty(&vlr->subscribers))
+ goto done;
+
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
+ LOGVLR(LOGL_ERROR, "Skipping Location Update expiry: Could not read current time\n");
+ goto done;
+ }
+
+ llist_for_each_entry_safe(vsub, vsub_tmp, &vlr->subscribers, list) {
+ if (vsub->expire_lu == VLR_SUBSCRIBER_NO_EXPIRATION || vsub->expire_lu > now.tv_sec)
+ continue;
+
+ LOGVLR(LOGL_DEBUG, "%s: Location Update expired\n", vlr_subscr_name(vsub));
+ vlr_rate_ctr_inc(vlr, VLR_CTR_DETACH_BY_T3212);
+ vlr_subscr_detach(vsub);
+ }
+
+done:
+ osmo_timer_schedule(&vlr->lu_expire_timer, VLR_SUBSCRIBER_LU_EXPIRATION_INTERVAL, 0);
+}
+
+/***********************************************************************
+ * PDP context data
+ ***********************************************************************/
+
+#define GSM_APN_LENGTH 102
+
+/* see GSM 09.02, 17.7.1, PDP-Context and GPRSSubscriptionData */
+/* see GSM 09.02, B.1, gprsSubscriptionData */
+struct sgsn_subscriber_pdp_data {
+ struct llist_head list;
+
+ unsigned int context_id;
+ enum gsm48_pdp_type_org pdp_type_org;
+ enum gsm48_pdp_type_nr pdp_type_nr;
+ struct osmo_sockaddr pdp_address[2];
+ char apn_str[GSM_APN_LENGTH];
+ uint8_t qos_subscribed[20];
+ size_t qos_subscribed_len;
+};
+
+struct sgsn_subscriber_pdp_data *
+vlr_subscr_pdp_data_alloc(struct vlr_subscr *vsub)
+{
+ struct sgsn_subscriber_pdp_data* pdata;
+
+ pdata = talloc_zero(vsub, struct sgsn_subscriber_pdp_data);
+
+ llist_add_tail(&pdata->list, &vsub->ps.pdp_list);
+ vlr_stat_item_inc(vsub->vlr, VLR_STAT_PDP_COUNT);
+
+ return pdata;
+}
+
+static int vlr_subscr_pdp_data_clear(struct vlr_subscr *vsub)
+{
+ struct sgsn_subscriber_pdp_data *pdp, *pdp2;
+ int count = 0;
+
+ llist_for_each_entry_safe(pdp, pdp2, &vsub->ps.pdp_list, list) {
+ llist_del(&pdp->list);
+ vlr_stat_item_dec(vsub->vlr, VLR_STAT_PDP_COUNT);
+ talloc_free(pdp);
+ count += 1;
+ }
+
+ return count;
+}
+
+static struct sgsn_subscriber_pdp_data *
+vlr_subscr_pdp_data_get_by_id(struct vlr_subscr *vsub, unsigned context_id)
+{
+ struct sgsn_subscriber_pdp_data *pdp;
+
+ llist_for_each_entry(pdp, &vsub->ps.pdp_list, list) {
+ if (pdp->context_id == context_id)
+ return pdp;
+ }
+
+ return NULL;
+}
+
+/***********************************************************************
+ * Actual Implementation
+ ***********************************************************************/
+
+static int vlr_rx_gsup_unknown_imsi(struct vlr_instance *vlr,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) {
+ LOGVLR(LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP request "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ gsup_client_mux_tx_error_reply(vlr->gcm, gsup_msg, GMM_CAUSE_IMSI_UNKNOWN);
+ } else if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGVLR(LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP error "
+ "of type 0x%02x, cause '%s' (%d)\n",
+ gsup_msg->imsi, gsup_msg->message_type,
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ } else {
+ LOGVLR(LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP response "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ }
+
+ return -GMM_CAUSE_IMSI_UNKNOWN;
+}
+
+static int vlr_rx_gsup_purge_no_subscr(struct vlr_instance *vlr,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGGSUPP(LOGL_NOTICE, gsup_msg,
+ "Purge MS has failed with cause '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ return -gsup_msg->cause;
+ }
+ LOGGSUPP(LOGL_INFO, gsup_msg, "Completing purge MS\n");
+ return 0;
+}
+
+/* VLR internal call to request UpdateLocation from HLR */
+int vlr_subscr_req_lu(struct vlr_subscr *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+ int rc;
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_UL_REQ);
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
+ gsup_msg.cn_domain = vlr_is_cs(vsub->vlr) ? OSMO_GSUP_CN_DOMAIN_CS : OSMO_GSUP_CN_DOMAIN_PS;
+ rc = vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+
+ return rc;
+}
+
+/* VLR internal call to request tuples from HLR */
+int vlr_subscr_req_sai(struct vlr_subscr *vsub,
+ const uint8_t *auts, const uint8_t *auts_rand)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_SAI_REQ);
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+ gsup_msg.auts = auts;
+ gsup_msg.rand = auts_rand;
+ gsup_msg.cn_domain = vlr_is_cs(vsub->vlr) ? OSMO_GSUP_CN_DOMAIN_CS : OSMO_GSUP_CN_DOMAIN_PS;
+
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+}
+
+/* Initiate Check_IMEI_VLR Procedure (23.018 Chapter 7.1.2.9) */
+int vlr_subscr_tx_req_check_imei(const struct vlr_subscr *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT,
+ .message_type = OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST,
+ };
+ uint8_t imei_enc[GSM23003_IMEI_NUM_DIGITS+2]; /* +2: IE header */
+ int len;
+
+ /* Encode IMEI */
+ len = gsm48_encode_bcd_number(imei_enc, sizeof(imei_enc), 0, vsub->imei);
+ if (len < 1) {
+ LOGVSUBP(LOGL_ERROR, vsub, "Error: cannot encode IMEI '%s'\n", vsub->imei);
+ return -ENOSPC;
+ }
+ gsup_msg.imei_enc = imei_enc;
+ gsup_msg.imei_enc_len = len;
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_CHECK_IMEI_REQ);
+
+ /* Send CHECK_IMEI_REQUEST */
+ OSMO_STRLCPY_ARRAY(gsup_msg.imsi, vsub->imsi);
+ return gsup_client_mux_tx(vsub->vlr->gcm, &gsup_msg);
+}
+
+/* Tell HLR that authentication failure occurred */
+int vlr_subscr_tx_auth_fail_rep(const struct vlr_subscr *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT,
+ .message_type = OSMO_GSUP_MSGT_AUTH_FAIL_REPORT,
+ .cn_domain = vlr_is_cs(vsub->vlr) ? OSMO_GSUP_CN_DOMAIN_CS : OSMO_GSUP_CN_DOMAIN_PS,
+ };
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_AUTH_FAIL_REP);
+
+ OSMO_STRLCPY_ARRAY(gsup_msg.imsi, vsub->imsi);
+ return gsup_client_mux_tx(vsub->vlr->gcm, &gsup_msg);
+}
+
+/* Update the subscriber with GSUP-received auth tuples */
+void vlr_subscr_update_tuples(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ unsigned int i;
+ unsigned int got_tuples;
+
+ if (gsup->num_auth_vectors) {
+ memset(&vsub->auth_tuples, 0, sizeof(vsub->auth_tuples));
+ for (i = 0; i < ARRAY_SIZE(vsub->auth_tuples); i++)
+ vsub->auth_tuples[i].key_seq = VLR_KEY_SEQ_INVAL;
+ }
+
+ got_tuples = 0;
+ for (i = 0; i < gsup->num_auth_vectors; i++) {
+ size_t key_seq = i;
+
+ if (key_seq >= ARRAY_SIZE(vsub->auth_tuples)) {
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "Skipping auth tuple with invalid cksn %zu\n",
+ key_seq);
+ continue;
+ }
+ vsub->auth_tuples[i].vec = gsup->auth_vectors[i];
+ vsub->auth_tuples[i].key_seq = key_seq;
+ got_tuples++;
+ }
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "Received %u auth tuples\n", got_tuples);
+ vlr_rate_ctr_add(vsub->vlr, VLR_CTR_GSUP_RX_TUPLES, got_tuples);
+
+ if (!got_tuples) {
+ /* FIXME what now? */
+ // vlr_subscr_cancel(vsub, GMM_CAUSE_GSM_AUTH_UNACCEPT); ?
+ }
+
+ /* New tuples means last_tuple becomes invalid */
+ vsub->last_tuple = NULL;
+}
+
+/* Handle SendAuthInfo Result/Error from HLR */
+static int vlr_subscr_handle_sai_res(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct osmo_fsm_inst *auth_fi = vsub->auth_fsm;
+ void *data = (void *) gsup;
+
+ if (!auth_fi) {
+ LOGVSUBP(LOGL_ERROR, vsub, "Received GSUP %s, but there is no auth_fsm\n",
+ osmo_gsup_message_type_name(gsup->message_type));
+ return -1;
+ }
+
+ switch (gsup->message_type) {
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ osmo_fsm_inst_dispatch(auth_fi, VLR_AUTH_E_HLR_SAI_ACK, data);
+ break;
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ osmo_fsm_inst_dispatch(auth_fi, VLR_AUTH_E_HLR_SAI_NACK, data);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static void vlr_subscr_gsup_insert_data(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ unsigned idx;
+ int rc;
+
+ if (gsup_msg->msisdn_enc_len) {//FIXME: vlr_subscr_set_msisdn()?
+ gsm48_decode_bcd_number2(vsub->msisdn, sizeof(vsub->msisdn),
+ gsup_msg->msisdn_enc,
+ gsup_msg->msisdn_enc_len, 0);
+ LOGVLR(LOGL_DEBUG, "IMSI:%s has MSISDN:%s\n",
+ vsub->imsi, vsub->msisdn);
+ }
+
+ if (gsup_msg->hlr_enc) {
+ if (gsup_msg->hlr_enc_len > sizeof(vsub->hlr.buf)) {
+ LOGVLR(LOGL_ERROR, "HLR-Number too long (%zu)\n",
+ gsup_msg->hlr_enc_len);
+ vsub->hlr.len = 0;
+ } else {
+ memcpy(vsub->hlr.buf, gsup_msg->hlr_enc,
+ gsup_msg->hlr_enc_len);
+ vsub->hlr.len = gsup_msg->hlr_enc_len;
+ }
+ }
+
+ if (gsup_msg->pdp_info_compl) {
+ rc = vlr_subscr_pdp_data_clear(vsub);
+ if (rc > 0)
+ LOGVLR(LOGL_INFO, "Cleared existing PDP info\n");
+ }
+
+ for (idx = 0; idx < gsup_msg->num_pdp_infos; idx++) {
+ const struct osmo_gsup_pdp_info *pdp_info = &gsup_msg->pdp_infos[idx];
+ size_t ctx_id = pdp_info->context_id;
+ struct sgsn_subscriber_pdp_data *pdp_data;
+
+ if (pdp_info->apn_enc_len >= sizeof(pdp_data->apn_str)-1) {
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "APN too long, context id = %zu, APN = %s\n",
+ ctx_id, osmo_hexdump(pdp_info->apn_enc,
+ pdp_info->apn_enc_len));
+ continue;
+ }
+
+ if (pdp_info->qos_enc_len > sizeof(pdp_data->qos_subscribed)) {
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "QoS info too long (%zu)\n",
+ pdp_info->qos_enc_len);
+ continue;
+ }
+
+ LOGVSUBP(LOGL_INFO, vsub,
+ "Will set PDP info, context id = %zu, APN = %s\n",
+ ctx_id, osmo_hexdump(pdp_info->apn_enc, pdp_info->apn_enc_len));
+
+ /* Set PDP info [ctx_id] */
+ pdp_data = vlr_subscr_pdp_data_get_by_id(vsub, ctx_id);
+ if (!pdp_data) {
+ pdp_data = vlr_subscr_pdp_data_alloc(vsub);
+ pdp_data->context_id = ctx_id;
+ }
+
+ OSMO_ASSERT(pdp_data != NULL);
+ pdp_data->pdp_type_org = pdp_info->pdp_type_org;
+ pdp_data->pdp_type_nr = pdp_info->pdp_type_nr;
+ memcpy(&pdp_data->pdp_address[0], &pdp_info->pdp_address[0], sizeof(pdp_data->pdp_address[0]));
+ memcpy(&pdp_data->pdp_address[1], &pdp_info->pdp_address[1], sizeof(pdp_data->pdp_address[1]));
+ osmo_apn_to_str(pdp_data->apn_str,
+ pdp_info->apn_enc, pdp_info->apn_enc_len);
+ memcpy(pdp_data->qos_subscribed, pdp_info->qos_enc, pdp_info->qos_enc_len);
+ pdp_data->qos_subscribed_len = pdp_info->qos_enc_len;
+ }
+}
+
+
+/* Handle InsertSubscrData Result from HLR */
+static int vlr_subscr_handle_isd_req(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_ISD_RES);
+
+ vlr_subscr_gsup_insert_data(vsub, gsup);
+ vsub->vlr->ops.subscr_update(vsub);
+
+ gsup_reply.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_reply);
+}
+
+/* Handle UpdateLocation Result from HLR */
+static int vlr_subscr_handle_lu_res(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct sgs_lu_response sgs_lu_response = {0};
+ 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;
+ }
+
+ /* contrary to MAP, we allow piggy-backing subscriber data onto the
+ * UPDATE LOCATION RESULT, and don't mandate the use of a separate
+ * nested INSERT SUBSCRIBER DATA transaction */
+ vlr_subscr_gsup_insert_data(vsub, gsup);
+
+ 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;
+}
+
+/* Handle UpdateLocation Result from HLR */
+static int vlr_subscr_handle_lu_err(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct sgs_lu_response sgs_lu_response = {0};
+ 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;
+ }
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "UpdateLocation failed; gmm_cause: %s\n",
+ get_value_string(gsm48_gmm_cause_names, 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;
+}
+
+enum gsm48_reject_value vlr_gmm_cause_to_reject_cause_domain(enum gsm48_gmm_cause gmm_cause, bool is_cs)
+{
+ enum gsm48_reject_value reject_cause = vlr_gmm_cause_to_reject_cause(gmm_cause);
+ if (is_cs)
+ return vlr_reject_causes_cs(reject_cause);
+ else
+ return vlr_reject_causes_ps(reject_cause);
+}
+
+enum gsm48_reject_value vlr_gmm_cause_to_reject_cause(enum gsm48_gmm_cause gmm_cause)
+{
+ switch (gmm_cause) {
+ case GMM_CAUSE_IMSI_UNKNOWN:
+ return GSM48_REJECT_IMSI_UNKNOWN_IN_HLR;
+ case GMM_CAUSE_ILLEGAL_MS:
+ return GSM48_REJECT_ILLEGAL_MS;
+ case GMM_CAUSE_IMEI_NOT_ACCEPTED:
+ return GSM48_REJECT_IMEI_NOT_ACCEPTED;
+ case GMM_CAUSE_ILLEGAL_ME:
+ return GSM48_REJECT_ILLEGAL_ME;
+ case GMM_CAUSE_GPRS_NOTALLOWED:
+ return GSM48_REJECT_GPRS_NOT_ALLOWED;
+ case GMM_CAUSE_GPRS_OTHER_NOTALLOWED:
+ return GSM48_REJECT_SERVICES_NOT_ALLOWED;
+ case GMM_CAUSE_MS_ID_NOT_DERIVED:
+ return GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE;
+ case GMM_CAUSE_IMPL_DETACHED:
+ return GSM48_REJECT_IMPLICITLY_DETACHED;
+ case GMM_CAUSE_PLMN_NOTALLOWED:
+ return GSM48_REJECT_PLMN_NOT_ALLOWED;
+ case GMM_CAUSE_LA_NOTALLOWED:
+ return GSM48_REJECT_LOC_NOT_ALLOWED;
+ case GMM_CAUSE_ROAMING_NOTALLOWED:
+ return GSM48_REJECT_ROAMING_NOT_ALLOWED;
+ case GMM_CAUSE_NO_GPRS_PLMN:
+ return GSM48_REJECT_GPRS_NOT_ALLOWED_IN_PLMN;
+ case GMM_CAUSE_MSC_TEMP_NOTREACH:
+ return GSM48_REJECT_MSC_TMP_NOT_REACHABLE;
+ case GMM_CAUSE_SYNC_FAIL:
+ return GSM48_REJECT_SYNCH_FAILURE;
+ case GMM_CAUSE_CONGESTION:
+ return GSM48_REJECT_CONGESTION;
+ case GMM_CAUSE_SEM_INCORR_MSG:
+ return GSM48_REJECT_INCORRECT_MESSAGE;
+ case GMM_CAUSE_INV_MAND_INFO:
+ return GSM48_REJECT_INVALID_MANDANTORY_INF;
+ case GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL:
+ return GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED;
+ case GMM_CAUSE_MSGT_INCOMP_P_STATE:
+ return GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE;
+ case GMM_CAUSE_IE_NOTEXIST_NOTIMPL:
+ return GSM48_REJECT_INF_ELEME_NOT_IMPLEMENTED;
+ case GMM_CAUSE_COND_IE_ERR:
+ return GSM48_REJECT_CONDTIONAL_IE_ERROR;
+ case GMM_CAUSE_MSG_INCOMP_P_STATE:
+ return GSM48_REJECT_MSG_NOT_COMPATIBLE;
+ case GMM_CAUSE_PROTO_ERR_UNSPEC:
+ return GSM48_REJECT_PROTOCOL_ERROR;
+ case GMM_CAUSE_NO_SUIT_CELL_IN_LA:
+ return GSM48_REJECT_NO_SUIT_CELL_IN_LA;
+ case GMM_CAUSE_MAC_FAIL:
+ return GSM48_REJECT_MAC_FAILURE;
+ case GMM_CAUSE_GSM_AUTH_UNACCEPT:
+ return GSM48_REJECT_GSM_AUTH_UNACCEPTABLE;
+ case GMM_CAUSE_NOT_AUTH_FOR_CSG:
+ return GSM48_REJECT_NOT_AUTH_FOR_CSG;
+ case GMM_CAUSE_SMS_VIA_GPRS_IN_RA:
+ return GSM48_REJECT_SMS_PROV_VIA_GPRS_IN_RA;
+ case GMM_CAUSE_NO_PDP_ACTIVATED:
+ return GSM48_REJECT_NO_PDP_CONTEXT_ACTIVATED;
+ case GMM_CAUSE_NET_FAIL:
+ return GSM48_REJECT_NETWORK_FAILURE;
+ default:
+ return GSM48_REJECT_NETWORK_FAILURE;
+ }
+}
+
+enum gsm48_reject_value vlr_reject_causes_ps(enum gsm48_reject_value reject_cause)
+{
+ switch (reject_cause) {
+ case GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED:
+ return GSM48_REJECT_NETWORK_FAILURE;
+ default:
+ return reject_cause;
+ }
+}
+
+enum gsm48_reject_value vlr_reject_causes_cs(enum gsm48_reject_value reject_cause)
+{
+ switch (reject_cause) {
+ case GSM48_REJECT_NO_SUIT_CELL_IN_LA:
+ case GSM48_REJECT_MAC_FAILURE:
+ case GSM48_REJECT_GSM_AUTH_UNACCEPTABLE:
+ case GSM48_REJECT_NOT_AUTH_FOR_CSG:
+ case GSM48_REJECT_SMS_PROV_VIA_GPRS_IN_RA:
+ case GSM48_REJECT_NO_PDP_CONTEXT_ACTIVATED:
+ return GSM48_REJECT_NETWORK_FAILURE;
+ default:
+ return reject_cause;
+ }
+}
+
+/* Handle LOCATION CANCEL request from HLR */
+static int vlr_subscr_handle_cancel_req(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ enum gsm48_reject_value gsm48_rej;
+ enum osmo_fsm_term_cause fsm_cause = OSMO_FSM_TERM_ERROR;
+ struct vlr_instance *vlr = vsub->vlr;
+ struct osmo_gsup_message gsup_reply = {0};
+ int is_update_procedure = !gsup_msg->cancel_type ||
+ gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE;
+
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_TX_CANCEL_RES);
+
+ LOGVSUBP(LOGL_INFO, vsub, "Cancelling MS subscriber (%s)\n",
+ is_update_procedure ?
+ "update procedure" : "subscription withdraw");
+
+ gsm48_rej = vlr_gmm_cause_to_reject_cause_domain(gsup_msg->cause, vlr_is_cs(vlr));
+ vlr_subscr_cancel_attach_fsm(vsub, fsm_cause, gsm48_rej);
+
+ if (vlr->ops.subscr_inval)
+ vlr->ops.subscr_inval(vsub->msc_conn_ref, vsub, gsm48_rej, is_update_procedure);
+
+ vlr_rate_ctr_inc(vlr, VLR_CTR_DETACH_BY_CANCEL);
+ vlr_subscr_detach(vsub);
+
+ gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT;
+ gsup_reply.cn_domain = vlr_is_cs(vlr) ? OSMO_GSUP_CN_DOMAIN_CS : OSMO_GSUP_CN_DOMAIN_PS;
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_reply);
+}
+
+/* Handle Check_IMEI_VLR result and error from HLR */
+static int vlr_subscr_handle_check_imei(struct vlr_subscr *vsub, const struct osmo_gsup_message *gsup)
+{
+ if (!vsub->lu_fsm) {
+ LOGVSUBP(LOGL_ERROR, vsub, "Rx %s without LU in progress\n",
+ osmo_gsup_message_type_name(gsup->message_type));
+ return -ENODEV;
+ }
+
+ /* Dispatch result to vsub->lu_fsm, which will either handle the result by itself (Check IMEI early) or dispatch
+ * it further to lu_compl_vlr_fsm (Check IMEI after LU). */
+ if (gsup->message_type == OSMO_GSUP_MSGT_CHECK_IMEI_RESULT) {
+ if (gsup->imei_result == OSMO_GSUP_IMEI_RESULT_ACK)
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_IMEI_ACK, NULL);
+ else
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_IMEI_NACK, NULL);
+ } else {
+ LOGVSUBP(LOGL_ERROR, vsub, "Check_IMEI_VLR 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_IMEI_NACK, NULL);
+ }
+
+ return 0;
+}
+
+/* Incoming handler for GSUP from HLR.
+ * Keep this function non-static for direct invocation by unit tests. */
+int vlr_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup)
+{
+ struct vlr_instance *vlr = data;
+ struct vlr_subscr *vsub;
+ int rc = 0;
+
+ vsub = vlr_subscr_find_by_imsi(vlr, gsup->imsi, __func__);
+ if (!vsub) {
+ switch (gsup->message_type) {
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_PURGE_NO_SUBSCR);
+ return vlr_rx_gsup_purge_no_subscr(vlr, gsup);
+ default:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UNKNOWN_IMSI);
+ return vlr_rx_gsup_unknown_imsi(vlr, gsup);
+ }
+ }
+
+ switch (gsup->message_type) {
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_SAI_RES);
+ rc = vlr_subscr_handle_sai_res(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_SAI_ERR);
+ rc = vlr_subscr_handle_sai_res(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_ISD_REQ);
+ rc = vlr_subscr_handle_isd_req(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_CANCEL_REQ);
+ rc = vlr_subscr_handle_cancel_req(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UL_RES);
+ rc = vlr_subscr_handle_lu_res(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UL_ERR);
+ rc = vlr_subscr_handle_lu_err(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_PURGE_MS_ERR);
+ goto out_unimpl;
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_PURGE_MS_RES);
+ goto out_unimpl;
+ case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_DELETE_DATA_REQ);
+ goto out_unimpl;
+ case OSMO_GSUP_MSGT_CHECK_IMEI_ERROR:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_CHECK_IMEI_ERR);
+ rc = vlr_subscr_handle_check_imei(vsub, gsup);
+ break;
+ case OSMO_GSUP_MSGT_CHECK_IMEI_RESULT:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_CHECK_IMEI_RES);
+ rc = vlr_subscr_handle_check_imei(vsub, gsup);
+ break;
+ default:
+ vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UNKNOWN);
+ LOGP(DLGSUP, LOGL_ERROR, "GSUP Message type not handled by VLR: %d\n", gsup->message_type);
+ rc = -EINVAL;
+ break;
+ }
+
+ vlr_subscr_put(vsub, __func__);
+ return rc;
+
+out_unimpl:
+ LOGVSUBP(LOGL_ERROR, vsub, "Rx GSUP msg_type=%d not yet implemented\n", gsup->message_type);
+ vlr_subscr_put(vsub, __func__);
+ return -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+}
+
+/* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */
+int vlr_subscr_rx_id_resp(struct vlr_subscr *vsub, const struct osmo_mobile_identity *mi)
+{
+ /* update the vlr_subscr with the given identity */
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ if (vsub->imsi[0]
+ && !vlr_subscr_matches_imsi(vsub, mi->imsi)) {
+ LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP differs:"
+ " %s\n", mi->imsi);
+ /* XXX Should we return an error, e.g. -EINVAL ? */
+ } else
+ vlr_subscr_set_imsi(vsub, mi->imsi);
+ break;
+ case GSM_MI_TYPE_IMEI:
+ vlr_subscr_set_imei(vsub, mi->imei);
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ vlr_subscr_set_imeisv(vsub, mi->imeisv);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (vsub->auth_fsm) {
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ return osmo_fsm_inst_dispatch(vsub->auth_fsm,
+ VLR_AUTH_E_MS_ID_IMSI, (void*)mi->imsi);
+ break;
+ }
+ }
+
+ if (vsub->lu_fsm) {
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_ID_IMSI, (void*)mi->imsi);
+ case GSM_MI_TYPE_IMEI:
+ return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_ID_IMEI, (void*)mi->imei);
+ case GSM_MI_TYPE_IMEISV:
+ return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_ID_IMEISV, (void*)mi->imeisv);
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */
+int vlr_subscr_rx_tmsi_reall_compl(struct vlr_subscr *vsub)
+{
+ if (vsub->lu_fsm) {
+ return osmo_fsm_inst_dispatch(vsub->lu_fsm,
+ VLR_ULA_E_NEW_TMSI_ACK, NULL);
+ } else if (vsub->proc_arq_fsm) {
+ return osmo_fsm_inst_dispatch(vsub->proc_arq_fsm,
+ PR_ARQ_E_TMSI_ACK, NULL);
+ } else {
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "gratuitous TMSI REALLOC COMPL\n");
+ return -EINVAL;
+ }
+}
+
+/* SGSN->VLR: Subscriber has provided ATTACH/RAU Complete */
+int vlr_subscr_rx_rau_complete(struct vlr_subscr *vsub)
+{
+ if (!vsub->lu_fsm)
+ return -EINVAL;
+
+ return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_NEW_TMSI_ACK, NULL);
+}
+
+bool vlr_subscr_expire(struct vlr_subscr *vsub)
+{
+ if (vsub->lu_complete) {
+ /* balancing the get from vlr_lu_compl_fsm_success() */
+ vsub->lu_complete = false;
+ vlr_subscr_put(vsub, VSUB_USE_ATTACHED);
+
+ return true;
+ }
+
+ return false;
+}
+
+static int vlr_subscr_detach(struct vlr_subscr *vsub)
+{
+ /* paranoia: should any LU or PARQ FSMs still be running, stop them. */
+ vlr_subscr_cancel_attach_fsm(vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION);
+
+ 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;
+}
+
+/* See TS 23.012 version 9.10.0 4.3.2.1 "Process Detach_IMSI_VLR" */
+int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub)
+{
+ int rc = 0;
+
+ vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_DETACH_BY_REQ);
+
+ if (!vsub->imsi_detached_flag)
+ rc = vlr_subscr_purge(vsub);
+
+ rc |= vlr_subscr_detach(vsub);
+ return rc;
+}
+
+/* Tear down any running FSMs due to MSC connection timeout.
+ * Visit all vsub->*_fsm pointers and give them a queue to send a final reject
+ * message before the entire connection is torn down.
+ * \param[in] vsub subscriber to tear down
+ */
+void vlr_ran_conn_timeout(struct vlr_subscr *vsub)
+{
+ vlr_subscr_cancel_attach_fsm(vsub, OSMO_FSM_TERM_TIMEOUT, GSM48_REJECT_CONGESTION);
+}
+
+struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops, bool is_ps)
+{
+ struct vlr_instance *vlr = talloc_zero(ctx, struct vlr_instance);
+ OSMO_ASSERT(vlr);
+
+ /* Some of these are needed only on UTRAN, but in case the caller wants
+ * only GERAN, she should just provide dummy callbacks. */
+ OSMO_ASSERT(ops->tx_auth_req);
+ OSMO_ASSERT(ops->tx_auth_rej);
+ OSMO_ASSERT(ops->tx_id_req);
+ OSMO_ASSERT(ops->tx_lu_acc);
+ OSMO_ASSERT(ops->tx_lu_rej);
+ OSMO_ASSERT(ops->tx_cm_serv_acc);
+ OSMO_ASSERT(ops->tx_cm_serv_rej);
+ OSMO_ASSERT(ops->set_ciph_mode);
+ OSMO_ASSERT(ops->tx_common_id);
+ OSMO_ASSERT(ops->subscr_update);
+ OSMO_ASSERT(ops->subscr_assoc);
+
+ INIT_LLIST_HEAD(&vlr->subscribers);
+ INIT_LLIST_HEAD(&vlr->operations);
+ memcpy(&vlr->ops, ops, sizeof(vlr->ops));
+
+ /* defaults */
+ vlr->cfg.is_ps = is_ps;
+ vlr->cfg.assign_tmsi = true;
+ vlr->cfg.nri_bitlen = OSMO_NRI_BITLEN_DEFAULT;
+ vlr->cfg.nri_ranges = osmo_nri_ranges_alloc(vlr);
+
+ vlr->statg = osmo_stat_item_group_alloc(vlr, &vlr_statg_desc, 0);
+ if (!vlr->statg)
+ goto err_free;
+
+ vlr->ctrg = rate_ctr_group_alloc(vlr, &vlr_ctrg_desc, 0);
+ if (!vlr->ctrg)
+ goto err_statg;
+
+ /* reset shared timer definitions */
+ osmo_tdefs_reset(msc_tdefs_vlr);
+ osmo_tdefs_reset(sgsn_tdefs_vlr);
+
+ /* osmo_auth_fsm.c */
+ vlr_auth_fsm_init(is_ps);
+
+ /* osmo_lu_fsm.c */
+ vlr_lu_fsm_init(is_ps);
+ /* vlr_access_request_fsm.c */
+ vlr_parq_fsm_init(is_ps);
+ /* vlr_sgs_fsm.c */
+ vlr_sgs_fsm_init();
+
+ if (is_ps)
+ vlr_tdefs = sgsn_tdefs_vlr;
+ else
+ vlr_tdefs = msc_tdefs_vlr;
+
+ return vlr;
+
+err_statg:
+ osmo_stat_item_group_free(vlr->statg);
+err_free:
+ talloc_free(vlr);
+ return NULL;
+}
+
+int vlr_start(struct vlr_instance *vlr, struct gsup_client_mux *gcm)
+{
+ OSMO_ASSERT(vlr);
+
+ vlr->gcm = gcm;
+ gcm->rx_cb[OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT] = (struct gsup_client_mux_rx_cb){
+ .func = vlr_gsup_rx,
+ .data = vlr,
+ };
+
+ osmo_timer_setup(&vlr->lu_expire_timer, vlr_subscr_expire_lu, vlr);
+ osmo_timer_schedule(&vlr->lu_expire_timer, VLR_SUBSCRIBER_LU_EXPIRATION_INTERVAL, 0);
+ return 0;
+}
+
+/* MSC->VLR: Subscriber has disconnected */
+int vlr_subscr_disconnected(struct vlr_subscr *vsub)
+{
+ /* This corresponds to a MAP-ABORT from MSC->VLR on a classic B
+ * interface */
+ if (vsub->lu_fsm)
+ osmo_fsm_inst_term(vsub->lu_fsm, OSMO_FSM_TERM_REQUEST, NULL);
+ if (vsub->auth_fsm)
+ osmo_fsm_inst_term(vsub->auth_fsm, OSMO_FSM_TERM_REQUEST, NULL);
+ vsub->msc_conn_ref = NULL;
+
+ return 0;
+}
+
+/* MSC->VLR: Receive Authentication Failure from Subscriber */
+int vlr_subscr_rx_auth_fail(struct vlr_subscr *vsub, const uint8_t *auts)
+{
+ struct vlr_auth_resp_par par = {0};
+ par.auts = auts;
+
+ osmo_fsm_inst_dispatch(vsub->auth_fsm, VLR_AUTH_E_MS_AUTH_FAIL, &par);
+ return 0;
+}
+
+/* MSC->VLR: Receive Authentication Response from MS
+ * \returns 1 in case of success, 0 in case of delay, -1 on auth error */
+int vlr_subscr_rx_auth_resp(struct vlr_subscr *vsub, bool is_r99,
+ bool is_utran, const uint8_t *res, uint8_t res_len)
+{
+ struct osmo_fsm_inst *auth_fi = vsub->auth_fsm;
+ struct vlr_auth_resp_par par;
+
+ par.is_r99 = is_r99;
+ par.is_utran = is_utran;
+ par.res = res;
+ par.res_len = res_len;
+ osmo_fsm_inst_dispatch(auth_fi, VLR_AUTH_E_MS_AUTH_RESP, (void *) &par);
+
+ return 0;
+}
+
+/* MSC->VLR: Receive result of Ciphering Mode Command from MS */
+void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, enum vlr_ciph_result_cause result)
+{
+ if (vsub->lu_fsm && vsub->lu_fsm->state == VLR_ULA_S_WAIT_CIPH)
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_CIPH_RES, &result);
+ if (vsub->proc_arq_fsm
+ && vsub->proc_arq_fsm->state == PR_ARQ_S_WAIT_CIPH)
+ osmo_fsm_inst_dispatch(vsub->proc_arq_fsm, PR_ARQ_E_CIPH_RES, &result);
+}
+
+/* Internal evaluation of requested ciphering mode.
+ * Send set_ciph_mode() to MSC depending on the ciph_mode argument.
+ * \param[in] vlr VLR instance.
+ * \param[in] fi Calling FSM instance, for logging.
+ * \param[in] msc_conn_ref MSC conn to send to.
+ * \param[in] ciph_mode Ciphering config, to decide whether to do ciphering.
+ * \returns 0 if no ciphering is needed or message was sent successfully,
+ * or a negative value if ciph_mode is invalid or sending failed.
+ */
+int vlr_set_ciph_mode(struct vlr_instance *vlr,
+ struct osmo_fsm_inst *fi,
+ void *msc_conn_ref,
+ bool umts_aka,
+ bool retrieve_imeisv)
+{
+ LOGPFSML(fi, LOGL_DEBUG, "Set Ciphering Mode\n");
+ return vlr->ops.set_ciph_mode(msc_conn_ref, umts_aka, retrieve_imeisv);
+}
+
+/* Decide whether UMTS AKA should be used.
+ * UTRAN networks are by definition R99 capable, and the auth vector is required to contain UMTS AKA
+ * tokens. This is expected to be verified by the caller. On GERAN, UMTS AKA must be used iff MS and
+ * GERAN are R99 capable and UMTS AKA tokens are available.
+ * \param[in] vec Auth tokens (received from the HLR).
+ * \param[in] is_r99 True when BTS and GERAN are R99 capable.
+ * \returns true to use UMTS AKA, false to use pre-R99 GSM AKA.
+ */
+bool vlr_use_umts_aka(struct osmo_auth_vector *vec, bool is_r99)
+{
+ if (!is_r99)
+ return false;
+ if (!(vec->auth_types & OSMO_AUTH_TYPE_UMTS))
+ return false;
+ return true;
+}
+
+void log_set_filter_vlr_subscr(struct log_target *target,
+ struct vlr_subscr *vlr_subscr)
+{
+ struct vlr_subscr **fsub = (void*)&target->filter_data[LOG_FLT_VLR_SUBSCR];
+ const char *use = "logfilter";
+
+ /* free the old data */
+ if (*fsub) {
+ vlr_subscr_put(*fsub, use);
+ *fsub = NULL;
+ }
+
+ if (vlr_subscr) {
+ target->filter_map |= (1 << LOG_FLT_VLR_SUBSCR);
+ vlr_subscr_get(vlr_subscr, use);
+ *fsub = vlr_subscr;
+ } else
+ target->filter_map &= ~(1 << LOG_FLT_VLR_SUBSCR);
+}
+
+int g_vlr_log_cat[_OSMO_VLR_LOGC_MAX];
+
+void osmo_vlr_set_log_cat(enum osmo_vlr_cat logc, int logc_num)
+{
+ if (logc < OSMO_VLR_LOGC_VLR || logc >= _OSMO_VLR_LOGC_MAX)
+ return;
+
+ g_vlr_log_cat[logc] = logc_num;
+
+ switch (logc) {
+ case OSMO_VLR_LOGC_VLR:
+ vlr_auth_fsm_set_log_subsys(logc_num);
+ vlr_parq_fsm_set_log_subsys(logc_num);
+ vlr_lu_fsm_set_log_subsys(logc_num);
+ break;
+ case OSMO_VLR_LOGC_SGS:
+ vlr_sgs_fsm_set_log_subsys(logc_num);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/src/libvlr/vlr_access_req_fsm.c b/src/libvlr/vlr_access_req_fsm.c
new file mode 100644
index 0000000..516f53d
--- /dev/null
+++ b/src/libvlr/vlr_access_req_fsm.c
@@ -0,0 +1,831 @@
+/* Osmocom Visitor Location Register (VLR): Access Request FSMs */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/vlr/vlr.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+#include "vlr_lu_fsm.h"
+#include "vlr_access_req_fsm.h"
+
+#define S(x) (1 << (x))
+
+/***********************************************************************
+ * Process_Access_Request_VLR, TS 29.002 Chapter 25.4.2
+ ***********************************************************************/
+
+static const struct value_string proc_arq_vlr_event_names[] = {
+ OSMO_VALUE_STRING(PR_ARQ_E_START),
+ OSMO_VALUE_STRING(PR_ARQ_E_ID_IMSI),
+ OSMO_VALUE_STRING(PR_ARQ_E_AUTH_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_AUTH_NO_INFO),
+ OSMO_VALUE_STRING(PR_ARQ_E_AUTH_FAILURE),
+ OSMO_VALUE_STRING(PR_ARQ_E_CIPH_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_UPD_LOC_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_TRACE_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_IMEI_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_PRES_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_TMSI_ACK),
+ { 0, NULL }
+};
+
+struct osmo_tdef_state_timeout msc_parq_tdef_states[32] = {
+ [PR_ARQ_S_WAIT_CHECK_IMEI] = { .T = 3270 },
+ [PR_ARQ_S_WAIT_OBTAIN_IMSI] = { .T = 3270 },
+};
+
+struct osmo_tdef_state_timeout sgsn_parq_tdef_states[32] = {
+ [PR_ARQ_S_WAIT_CHECK_IMEI] = { .T = 3370 },
+ [PR_ARQ_S_WAIT_OBTAIN_IMSI] = { .T = 3370 },
+};
+
+struct osmo_tdef_state_timeout *parq_fsm_state_tdef;
+
+struct proc_arq_priv {
+ struct vlr_instance *vlr;
+ struct vlr_subscr *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *ul_child_fsm;
+ struct osmo_fsm_inst *sub_pres_vlr_fsm;
+ uint32_t parent_event_success;
+ uint32_t parent_event_failure;
+ void *parent_event_data;
+
+ enum vlr_parq_type type;
+ enum osmo_cm_service_type cm_service_type;
+ enum gsm48_reject_value result; /*< 0 on success */
+ bool by_tmsi;
+ char imsi[16];
+ uint32_t tmsi;
+ struct osmo_location_area_id lai;
+ bool authentication_required;
+ /* is_ciphering_to_be_attempted: true when any A5/n > 0 are enabled. Ciphering is allowed, always attempt to get Auth Info from
+ * the HLR. */
+ bool is_ciphering_to_be_attempted;
+ /* is_ciphering_required: true when A5/0 is disabled. If we cannot get Auth Info from the HLR, reject the
+ * subscriber. */
+ bool is_ciphering_required;
+ uint8_t key_seq;
+ bool is_r99;
+ bool is_utran;
+ bool implicitly_accepted_parq_by_ciphering_cmd;
+};
+
+static int assoc_par_with_subscr(struct osmo_fsm_inst *fi, struct vlr_subscr *vsub)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+
+ vsub->msc_conn_ref = par->msc_conn_ref;
+ par->vsub = vsub;
+ /* Tell MSC to associate this subscriber with the given
+ * connection */
+ return vlr->ops.subscr_assoc(par->msc_conn_ref, par->vsub);
+}
+
+static const char *vlr_proc_arq_result_name(const struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ return par->result? gsm48_reject_value_name(par->result) : "PASSED";
+}
+
+#define proc_arq_fsm_done(fi, res) _proc_arq_fsm_done(fi, res, __FILE__, __LINE__)
+static void _proc_arq_fsm_done(struct osmo_fsm_inst *fi,
+ enum gsm48_reject_value gsm48_rej,
+ const char *file, int line)
+{
+ struct proc_arq_priv *par = fi->priv;
+ par->result = gsm48_rej;
+ LOGPFSMSRC(fi, file, line, "proc_arq_fsm_done(%s)\n", vlr_proc_arq_result_name(fi));
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_DONE, 0, 0);
+}
+
+static void proc_arq_vlr_dispatch_result(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct proc_arq_priv *par = fi->priv;
+ bool success;
+ int rc;
+ LOGPFSM(fi, "Process Access Request result: %s\n", vlr_proc_arq_result_name(fi));
+
+ success = (par->result == 0);
+
+ /* It would be logical to first dispatch the success event to the
+ * parent FSM, but that could start actions that send messages to the
+ * MS. Rather send the CM Service Accept message first and then signal
+ * success. Since messages are handled synchronously, the success event
+ * will be processed before we handle new incoming data from the MS. */
+
+ if (par->type == VLR_PR_ARQ_T_CM_SERV_REQ) {
+ if (success
+ && !par->implicitly_accepted_parq_by_ciphering_cmd) {
+ rc = par->vlr->ops.tx_cm_serv_acc(par->msc_conn_ref,
+ par->cm_service_type);
+ if (rc) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send CM Service Accept\n");
+ success = false;
+ }
+ }
+ if (!success) {
+ rc = par->vlr->ops.tx_cm_serv_rej(par->msc_conn_ref,
+ par->cm_service_type,
+ par->result);
+ if (rc)
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send CM Service Reject\n");
+ }
+ }
+
+ /* For VLR_PR_ARQ_T_PAGING_RESP, there is nothing to send. The conn_fsm
+ * will start handling pending paging transactions. */
+
+ if (!fi->proc.parent) {
+ LOGPFSML(fi, LOGL_ERROR, "No parent FSM\n");
+ return;
+ }
+ osmo_fsm_inst_dispatch(fi->proc.parent,
+ success ? par->parent_event_success
+ : par->parent_event_failure,
+ par->parent_event_data);
+}
+
+void proc_arq_vlr_cleanup(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause cause)
+{
+ struct proc_arq_priv *par = fi->priv;
+ if (par->vsub && par->vsub->proc_arq_fsm == fi)
+ par->vsub->proc_arq_fsm = NULL;
+}
+
+static void _proc_arq_vlr_post_imei(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* See 3GPP TS 29.002 Proc_Acc_Req_VLR3. */
+ /* TODO: Identity := IMSI */
+ if (0 /* TODO: TMSI reallocation at access: vlr->cfg.alloc_tmsi_arq */) {
+ vlr_subscr_alloc_tmsi(vsub);
+ /* TODO: forward TMSI to MS, wait for TMSI
+ * REALLOC COMPLETE */
+ /* TODO: Freeze old TMSI */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_TMSI_ACK, 0, 0);
+ return;
+ }
+
+ proc_arq_fsm_done(fi, 0);
+}
+
+static void _proc_arq_vlr_post_trace(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* Node 3 */
+ /* See 3GPP TS 29.002 Proc_Acc_Req_VLR3. */
+ if (0 /* IMEI check required */) {
+ /* Chck_IMEI_VLR */
+ vlr->ops.tx_id_req(par->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ osmo_tdef_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CHECK_IMEI, parq_fsm_state_tdef, vlr_tdefs, -1);
+ } else
+ _proc_arq_vlr_post_imei(fi);
+}
+
+/* After Subscriber_Present_VLR */
+static void _proc_arq_vlr_post_pres(struct osmo_fsm_inst *fi)
+{
+ LOGPFSM(fi, "%s()\n", __func__);
+ /* See 3GPP TS 29.002 Proc_Acc_Req_VLR3. */
+ if (0 /* TODO: tracing required */) {
+ /* TODO: Trace_Subscriber_Activity_VLR */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_TRACE_SUB, 0, 0);
+ }
+ _proc_arq_vlr_post_trace(fi);
+}
+
+/* After Update_Location_Child_VLR */
+static void _proc_arq_vlr_node2_post_vlr(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (!vsub->sub_dataconf_by_hlr_ind) {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, GSM48_REJECT_IMSI_UNKNOWN_IN_HLR);
+ return;
+ }
+ /* We don't feature location area specific blocking (yet). */
+ if (0 /* roaming not allowed in LA */) {
+ /* Set User Error: Roaming not allowed in this LA */
+ proc_arq_fsm_done(fi, GSM48_REJECT_ROAMING_NOT_ALLOWED);
+ return;
+ }
+ vsub->imsi_detached_flag = false;
+ if (vsub->ms_not_reachable_flag) {
+ /* Start Subscriber_Present_VLR */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_SUB_PRES, 0, 0);
+ sub_pres_vlr_fsm_start(&par->sub_pres_vlr_fsm, fi, vsub, PR_ARQ_E_PRES_RES);
+ return;
+ }
+ _proc_arq_vlr_post_pres(fi);
+}
+
+static void _proc_arq_vlr_node2_post_ciph(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+ int rc;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ rc = par->vlr->ops.tx_common_id(par->msc_conn_ref);
+ if (rc)
+ LOGPFSML(fi, LOGL_ERROR, "Error while sending Common ID (%d)\n", rc);
+
+ vsub->conf_by_radio_contact_ind = true;
+ if (vsub->loc_conf_in_hlr_ind == false) {
+ /* start Update_Location_Child_VLR. WE use
+ * Update_HLR_VLR instead, the differences appear
+ * insignificant for now. */
+ par->ul_child_fsm = upd_hlr_vlr_proc_start(fi, vsub,
+ PR_ARQ_E_UPD_LOC_RES);
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_UPD_LOC_CHILD, 0, 0);
+ return;
+ }
+ _proc_arq_vlr_node2_post_vlr(fi);
+}
+
+/* Return true when CipherModeCmd / SecurityModeCmd should be attempted. */
+static bool is_cmc_smc_to_be_attempted(struct proc_arq_priv *par)
+{
+ /* UTRAN: always send SecModeCmd, even if ciphering is not required.
+ * GERAN: avoid sending CiphModeCmd if ciphering is not required. */
+ return par->is_utran || par->is_ciphering_to_be_attempted;
+}
+
+static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+ bool umts_aka;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* Continue with ciphering, if enabled.
+ * If auth/ciph is optional and the HLR returned no auth info, continue without ciphering. */
+ if (!is_cmc_smc_to_be_attempted(par)
+ || (vsub->sec_ctx == VLR_SEC_CTX_NONE && !par->is_ciphering_required)) {
+ _proc_arq_vlr_node2_post_ciph(fi);
+ return;
+ }
+
+ switch (vsub->sec_ctx) {
+ case VLR_SEC_CTX_GSM:
+ umts_aka = false;
+ break;
+ case VLR_SEC_CTX_UMTS:
+ umts_aka = true;
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "Cannot start ciphering, security context is not established\n");
+ proc_arq_fsm_done(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ if (vlr_set_ciph_mode(vsub->vlr, fi, par->msc_conn_ref,
+ umts_aka,
+ vsub->vlr->cfg.retrieve_imeisv_ciphered)) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send Ciphering Mode Command\n");
+ proc_arq_fsm_done(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ par->implicitly_accepted_parq_by_ciphering_cmd = true;
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CIPH, 0, 0);
+}
+
+static bool is_auth_to_be_attempted(struct proc_arq_priv *par)
+{
+ /* The cases where the authentication procedure should be used
+ * are defined in 3GPP TS 33.102 */
+ /* For now we use a default value passed in to vlr_lu_fsm(). */
+ return par->authentication_required ||
+ (par->is_ciphering_to_be_attempted && !auth_try_reuse_tuple(par->vsub, par->key_seq));
+}
+
+/* after the IMSI is known */
+static void proc_arq_vlr_fn_post_imsi(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ /* TODO: Identity IMEI -> System Failure */
+ if (is_auth_to_be_attempted(par)) {
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_AUTH,
+ 0, 0);
+ vsub->auth_fsm = auth_fsm_start(vsub, fi,
+ PR_ARQ_E_AUTH_RES,
+ PR_ARQ_E_AUTH_NO_INFO,
+ PR_ARQ_E_AUTH_FAILURE,
+ par->is_r99,
+ par->is_utran);
+ } else {
+ _proc_arq_vlr_node2(fi);
+ }
+}
+
+static void proc_arq_vlr_fn_init(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+ struct vlr_subscr *vsub = NULL;
+
+ OSMO_ASSERT(event == PR_ARQ_E_START);
+
+ /* Obtain_Identity_VLR */
+ if (!par->by_tmsi) {
+ /* IMSI was included */
+ vsub = vlr_subscr_find_by_imsi(par->vlr, par->imsi, __func__);
+ } else {
+ /* TMSI was included */
+ vsub = vlr_subscr_find_by_tmsi(par->vlr, par->tmsi, __func__);
+ }
+ if (vsub) {
+ log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
+ if (vsub->proc_arq_fsm && fi != vsub->proc_arq_fsm) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Another proc_arq_fsm is already"
+ " associated with subscr %s,"
+ " terminating the other FSM.\n",
+ vlr_subscr_name(vsub));
+ proc_arq_fsm_done(vsub->proc_arq_fsm,
+ GSM48_REJECT_NETWORK_FAILURE);
+ }
+ vsub->proc_arq_fsm = fi;
+ if (assoc_par_with_subscr(fi, vsub) != 0)
+ proc_arq_fsm_done(fi, GSM48_REJECT_NETWORK_FAILURE);
+ else
+ proc_arq_vlr_fn_post_imsi(fi);
+ vlr_subscr_put(vsub, __func__);
+ return;
+ }
+ /* No VSUB could be resolved. What now? */
+
+ if (!par->by_tmsi) {
+ /* We couldn't find a subscriber even by IMSI,
+ * Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, GSM48_REJECT_IMSI_UNKNOWN_IN_VLR);
+ return;
+ } else {
+ /* TMSI was included, are we permitted to use it? */
+ if (vlr->cfg.parq_retrieve_imsi) {
+ /* Obtain_IMSI_VLR */
+ osmo_tdef_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_OBTAIN_IMSI, parq_fsm_state_tdef, vlr_tdefs, -1);
+ return;
+ } else {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, GSM48_REJECT_IMSI_UNKNOWN_IN_VLR);
+ return;
+ }
+ }
+}
+
+/* ID REQ(IMSI) has returned */
+static void proc_arq_vlr_fn_w_obt_imsi(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+ struct vlr_subscr *vsub;
+
+ OSMO_ASSERT(event == PR_ARQ_E_ID_IMSI);
+
+ vsub = vlr_subscr_find_by_imsi(vlr, par->imsi, __func__);
+ if (!vsub) {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, GSM48_REJECT_IMSI_UNKNOWN_IN_VLR);
+ return;
+ }
+ if (assoc_par_with_subscr(fi, vsub))
+ proc_arq_fsm_done(fi, GSM48_REJECT_NETWORK_FAILURE);
+ else
+ proc_arq_vlr_fn_post_imsi(fi);
+ vlr_subscr_put(vsub, __func__);
+}
+
+/* Authenticate_VLR has completed */
+static void proc_arq_vlr_fn_w_auth(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct proc_arq_priv *par = fi->priv;
+ enum gsm48_reject_value *cause = data;
+
+ switch (event) {
+ case PR_ARQ_E_AUTH_RES:
+ /* Node 2 */
+ _proc_arq_vlr_node2(fi);
+ return;
+
+ case PR_ARQ_E_AUTH_FAILURE:
+ proc_arq_fsm_done(fi, cause ? *cause : GSM48_REJECT_NETWORK_FAILURE);
+ return;
+
+ case PR_ARQ_E_AUTH_NO_INFO:
+ /* HLR returned no auth info for the subscriber. Continue only if authentication is optional. */
+ if (par->authentication_required) {
+ proc_arq_fsm_done(fi, cause ? *cause : GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+ LOGPFSML(fi, LOGL_INFO,
+ "Attaching subscriber without auth (auth is optional, and no auth info received from HLR)\n");
+ /* Node 2 */
+ _proc_arq_vlr_node2(fi);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void proc_arq_vlr_fn_w_ciph(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ enum vlr_ciph_result_cause result = VLR_CIPH_REJECT;
+
+ OSMO_ASSERT(event == PR_ARQ_E_CIPH_RES);
+
+ if (!data)
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: NULL\n");
+ else
+ result = *(enum vlr_ciph_result_cause*)data;
+
+ switch (result) {
+ case VLR_CIPH_COMPL:
+ _proc_arq_vlr_node2_post_ciph(fi);
+ return;
+ case VLR_CIPH_REJECT:
+ LOGPFSM(fi, "ciphering rejected\n");
+ proc_arq_fsm_done(fi, GSM48_REJECT_ILLEGAL_MS);
+ return;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: %d\n", result);
+ proc_arq_fsm_done(fi, GSM48_REJECT_ILLEGAL_MS);
+ return;
+ }
+}
+
+/* Update_Location_Child_VLR has completed */
+static void proc_arq_vlr_fn_w_upd_loc(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_UPD_LOC_RES);
+
+ _proc_arq_vlr_node2_post_vlr(fi);
+}
+
+/* Subscriber_Present_VLR has completed */
+static void proc_arq_vlr_fn_w_pres(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_PRES_RES);
+
+ _proc_arq_vlr_post_pres(fi);
+}
+
+static void proc_arq_vlr_fn_w_trace(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_TRACE_RES);
+
+ _proc_arq_vlr_post_trace(fi);
+}
+
+/* we have received the ID RESPONSE (IMEI) */
+static void proc_arq_vlr_fn_w_imei(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_IMEI_RES);
+
+ _proc_arq_vlr_post_imei(fi);
+}
+
+/* MSC tells us that MS has acknowleded TMSI re-allocation */
+static void proc_arq_vlr_fn_w_tmsi(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_TMSI_ACK);
+
+ /* FIXME: check confirmation? unfreeze? */
+ proc_arq_fsm_done(fi, 0);
+}
+
+static const struct osmo_fsm_state proc_arq_vlr_states[] = {
+ [PR_ARQ_S_INIT] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_INIT),
+ .in_event_mask = S(PR_ARQ_E_START),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_OBTAIN_IMSI) |
+ S(PR_ARQ_S_WAIT_AUTH) |
+ S(PR_ARQ_S_WAIT_CIPH) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_init,
+ },
+ [PR_ARQ_S_WAIT_OBTAIN_IMSI] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_OBTAIN_IMSI),
+ .in_event_mask = S(PR_ARQ_E_ID_IMSI),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_AUTH) |
+ S(PR_ARQ_S_WAIT_CIPH) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_obt_imsi,
+ },
+ [PR_ARQ_S_WAIT_AUTH] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_AUTH),
+ .in_event_mask = S(PR_ARQ_E_AUTH_RES) |
+ S(PR_ARQ_E_AUTH_NO_INFO) |
+ S(PR_ARQ_E_AUTH_FAILURE),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_CIPH) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_auth,
+ },
+ [PR_ARQ_S_WAIT_CIPH] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_CIPH),
+ .in_event_mask = S(PR_ARQ_E_CIPH_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_ciph,
+ },
+ [PR_ARQ_S_WAIT_UPD_LOC_CHILD] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_UPD_LOC_CHILD),
+ .in_event_mask = S(PR_ARQ_E_UPD_LOC_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_upd_loc,
+ },
+ [PR_ARQ_S_WAIT_SUB_PRES] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_SUB_PRES),
+ .in_event_mask = S(PR_ARQ_E_PRES_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_pres,
+ },
+ [PR_ARQ_S_WAIT_TRACE_SUB] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_TRACE_SUB),
+ .in_event_mask = S(PR_ARQ_E_TRACE_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_trace,
+ },
+ [PR_ARQ_S_WAIT_CHECK_IMEI] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_CHECK_IMEI),
+ .in_event_mask = S(PR_ARQ_E_IMEI_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_imei,
+ },
+ [PR_ARQ_S_WAIT_TMSI_ACK] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_TMSI_ACK),
+ .in_event_mask = S(PR_ARQ_E_TMSI_ACK),
+ .out_state_mask = S(PR_ARQ_S_DONE),
+ .action = proc_arq_vlr_fn_w_tmsi,
+ },
+ [PR_ARQ_S_DONE] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_DONE),
+ .onenter = proc_arq_vlr_dispatch_result,
+ },
+};
+
+static struct osmo_fsm proc_arq_vlr_fsm = {
+ .name = "Process_Access_Request_VLR",
+ .states = proc_arq_vlr_states,
+ .num_states = ARRAY_SIZE(proc_arq_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DLGLOBAL,
+ .event_names = proc_arq_vlr_event_names,
+ .cleanup = proc_arq_vlr_cleanup,
+};
+
+void
+vlr_proc_acc_req(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_parq_type type, enum osmo_cm_service_type cm_service_type,
+ const struct osmo_mobile_identity *mi,
+ const struct osmo_location_area_id *lai,
+ bool authentication_required,
+ bool is_ciphering_to_be_attempted,
+ bool is_ciphering_required,
+ uint8_t key_seq,
+ bool is_r99, bool is_utran)
+{
+ struct osmo_fsm_inst *fi;
+ struct proc_arq_priv *par;
+
+ if (is_ciphering_required)
+ OSMO_ASSERT(is_ciphering_to_be_attempted);
+
+ fi = osmo_fsm_inst_alloc_child(&proc_arq_vlr_fsm, parent,
+ parent_event_failure);
+ if (!fi)
+ return;
+
+ par = talloc_zero(fi, struct proc_arq_priv);
+ fi->priv = par;
+ par->vlr = vlr;
+ par->msc_conn_ref = msc_conn_ref;
+ par->type = type;
+ par->cm_service_type = cm_service_type;
+ par->lai = *lai;
+ par->parent_event_success = parent_event_success;
+ par->parent_event_failure = parent_event_failure;
+ par->parent_event_data = parent_event_data;
+ par->authentication_required = authentication_required;
+ par->is_ciphering_to_be_attempted = is_ciphering_to_be_attempted;
+ par->is_ciphering_required = is_ciphering_required;
+ par->key_seq = key_seq;
+ par->is_r99 = is_r99;
+ par->is_utran = is_utran;
+
+ LOGPFSM(fi, "rev=%s net=%s%s%s\n",
+ is_r99 ? "R99" : "GSM",
+ is_utran ? "UTRAN" : "GERAN",
+ (authentication_required || is_ciphering_to_be_attempted) ?
+ " Auth" : " (no Auth)",
+ (authentication_required || is_ciphering_to_be_attempted) ?
+ (is_ciphering_to_be_attempted ? "+Ciph" : " (no Ciph)")
+ : "");
+
+ if (is_utran && !authentication_required)
+ LOGPFSML(fi, LOGL_ERROR,
+ "Authentication off on UTRAN network. Good luck.\n");
+
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ OSMO_STRLCPY_ARRAY(par->imsi, mi->imsi);
+ par->by_tmsi = false;
+ break;
+ case GSM_MI_TYPE_TMSI:
+ par->by_tmsi = true;
+ par->tmsi = mi->tmsi;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ /* TODO: IMEI (emergency call) */
+ default:
+ proc_arq_fsm_done(fi, GSM48_REJECT_INVALID_MANDANTORY_INF);
+ return;
+ }
+
+ osmo_fsm_inst_dispatch(fi, PR_ARQ_E_START, NULL);
+}
+
+/* Gracefully terminate an FSM created by vlr_proc_acc_req() in case of
+ * external timeout (i.e. from MSC). */
+void vlr_parq_cancel(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause fsm_cause,
+ enum gsm48_reject_value gsm48_cause)
+{
+ if (!fi || fi->state == PR_ARQ_S_DONE)
+ return;
+ LOGPFSM(fi, "Cancel: %s\n", osmo_fsm_term_cause_name(fsm_cause));
+ proc_arq_fsm_done(fi, gsm48_cause);
+}
+
+
+#if 0
+/***********************************************************************
+ * Update_Location_Child_VLR, TS 29.002 Chapter 25.4.4
+ ***********************************************************************/
+
+enum upd_loc_child_vlr_state {
+ ULC_S_IDLE,
+ ULC_S_WAIT_HLR_RESP,
+ ULC_S_DONE,
+};
+
+enum upd_loc_child_vlr_event {
+ ULC_E_START,
+};
+
+static const struct value_string upd_loc_child_vlr_event_names[] = {
+ { ULC_E_START, "START" },
+ { 0, NULL }
+};
+
+static void upd_loc_child_f_idle(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ OSMO_ASSERT(event == ULC_E_START);
+
+ /* send update location */
+}
+
+static void upd_loc_child_f_w_hlr(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+}
+
+static const struct osmo_fsm_state upd_loc_child_vlr_states[] = {
+ [ULC_S_IDLE] = {
+ .in_event_mask = ,
+ .out_state_mask = S(ULC_S_WAIT_HLR_RESP) |
+ S(ULC_S_DONE),
+ .name = "IDLE",
+ .action = upd_loc_child_f_idle,
+ },
+ [ULC_S_WAIT_HLR_RESP] = {
+ .in_event_mask = ,
+ .out_state_mask = S(ULC_S_DONE),
+ .name = "WAIT-HLR-RESP",
+ .action = upd_loc_child_f_w_hlr,
+ },
+ [ULC_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+static struct osmo_fsm upd_loc_child_vlr_fsm = {
+ .name = "Update_Location_Child_VLR",
+ .states = upd_loc_child_vlr_states,
+ .num_states = ARRAY_SIZE(upd_loc_child_vlr_states),
+ .log_subsys = DVLR,
+ .event_names = upd_loc_child_vlr_event_names,
+};
+#endif
+
+void vlr_parq_fsm_init(bool is_ps)
+{
+ if (is_ps)
+ parq_fsm_state_tdef = sgsn_parq_tdef_states;
+ else
+ parq_fsm_state_tdef = msc_parq_tdef_states;
+
+ //OSMO_ASSERT(osmo_fsm_register(&upd_loc_child_vlr_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&proc_arq_vlr_fsm) == 0);
+}
+
+void vlr_parq_fsm_set_log_subsys(int log_subsys)
+{
+ proc_arq_vlr_fsm.log_subsys = log_subsys;
+}
diff --git a/src/libvlr/vlr_access_req_fsm.h b/src/libvlr/vlr_access_req_fsm.h
new file mode 100644
index 0000000..42b2c7e
--- /dev/null
+++ b/src/libvlr/vlr_access_req_fsm.h
@@ -0,0 +1,20 @@
+#pragma once
+
+enum proc_arq_vlr_state {
+ PR_ARQ_S_INIT,
+ /* Waiting for Obtain_Identity_VLR (IMSI) result */
+ PR_ARQ_S_WAIT_OBTAIN_IMSI,
+ /* Waiting for Authenticate_VLR result */
+ PR_ARQ_S_WAIT_AUTH,
+ PR_ARQ_S_WAIT_CIPH,
+ PR_ARQ_S_WAIT_UPD_LOC_CHILD,
+ PR_ARQ_S_WAIT_SUB_PRES,
+ PR_ARQ_S_WAIT_TRACE_SUB,
+ PR_ARQ_S_WAIT_CHECK_IMEI,
+ PR_ARQ_S_WAIT_TMSI_ACK,
+ PR_ARQ_S_WAIT_CECK_CONF,
+ PR_ARQ_S_DONE,
+};
+
+void vlr_parq_fsm_init(bool is_ps);
+void vlr_parq_fsm_set_log_subsys(int log_level);
diff --git a/src/libvlr/vlr_auth_fsm.c b/src/libvlr/vlr_auth_fsm.c
new file mode 100644
index 0000000..63fb04a
--- /dev/null
+++ b/src/libvlr/vlr_auth_fsm.c
@@ -0,0 +1,699 @@
+/* Osmocom Visitor Location Register (VLR) Authentication FSM */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/vlr/vlr.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+
+#define S(x) (1 << (x))
+
+static const struct value_string fsm_auth_event_names[] = {
+ OSMO_VALUE_STRING(VLR_AUTH_E_START),
+ OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_ACK),
+ OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_NACK),
+ OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_ABORT),
+ OSMO_VALUE_STRING(VLR_AUTH_E_MS_AUTH_RESP),
+ OSMO_VALUE_STRING(VLR_AUTH_E_MS_AUTH_FAIL),
+ OSMO_VALUE_STRING(VLR_AUTH_E_MS_ID_IMSI),
+ { 0, NULL }
+};
+
+struct osmo_tdef_state_timeout msc_auth_tdef_states[32] = {
+ [VLR_SUB_AS_WAIT_RESP] = { .T = 3260 },
+ [VLR_SUB_AS_WAIT_RESP_RESYNC] = { .T = 3260 },
+ [VLR_SUB_AS_WAIT_ID_IMSI] = { .T = 3270 },
+};
+
+struct osmo_tdef_state_timeout sgsn_auth_tdef_states[32] = {
+ [VLR_SUB_AS_WAIT_RESP] = { .T = 3360 },
+ [VLR_SUB_AS_WAIT_RESP_RESYNC] = { .T = 3360 },
+ [VLR_SUB_AS_WAIT_ID_IMSI] = { .T = 3370 },
+};
+
+struct osmo_tdef_state_timeout *auth_fsm_state_tdef;
+
+/* private state of the auth_fsm_instance */
+struct auth_fsm_priv {
+ struct vlr_subscr *vsub;
+ bool by_imsi;
+ bool is_r99;
+ bool is_utran;
+ bool auth_requested;
+
+ int auth_tuple_max_reuse_count; /* see vlr->cfg instead */
+
+ uint32_t parent_event_success;
+ uint32_t parent_event_no_auth_info;
+ uint32_t parent_event_failure;
+};
+
+/***********************************************************************
+ * Utility functions
+ ***********************************************************************/
+
+/* Always use either vlr_subscr_get_auth_tuple() or vlr_subscr_has_auth_tuple()
+ * instead, to ensure proper use count.
+ * Return an auth tuple with the lowest use_count among the auth tuples. If
+ * max_reuse_count >= 0, return NULL if all available auth tuples have a use
+ * count > max_reuse_count. If max_reuse_count is negative, return a currently
+ * least used auth tuple without enforcing a maximum use count. If there are
+ * no auth tuples, return NULL.
+ */
+static struct vlr_auth_tuple *
+_vlr_subscr_next_auth_tuple(struct vlr_subscr *vsub, int max_reuse_count)
+{
+ unsigned int count;
+ unsigned int idx;
+ struct vlr_auth_tuple *at = NULL;
+ unsigned int key_seq = VLR_KEY_SEQ_INVAL;
+
+ if (!vsub)
+ return NULL;
+
+ if (vsub->last_tuple)
+ key_seq = vsub->last_tuple->key_seq;
+
+ if (key_seq == VLR_KEY_SEQ_INVAL)
+ /* Start with 0 after increment modulo array size */
+ idx = ARRAY_SIZE(vsub->auth_tuples) - 1;
+ else
+ idx = key_seq;
+
+ for (count = ARRAY_SIZE(vsub->auth_tuples); count > 0; count--) {
+ idx = (idx + 1) % ARRAY_SIZE(vsub->auth_tuples);
+
+ if (vsub->auth_tuples[idx].key_seq == VLR_KEY_SEQ_INVAL)
+ continue;
+
+ if (!at || vsub->auth_tuples[idx].use_count < at->use_count)
+ at = &vsub->auth_tuples[idx];
+ }
+
+ if (!at || (max_reuse_count >= 0 && at->use_count > max_reuse_count))
+ return NULL;
+
+ return at;
+}
+
+/* Return an auth tuple and increment its use count. */
+static struct vlr_auth_tuple *
+vlr_subscr_get_auth_tuple(struct vlr_subscr *vsub, int max_reuse_count)
+{
+ struct vlr_auth_tuple *at = _vlr_subscr_next_auth_tuple(vsub,
+ max_reuse_count);
+ if (!at)
+ return NULL;
+ at->use_count++;
+ return at;
+}
+
+/* Return whether an auth tuple with a matching use_count is available. */
+static bool vlr_subscr_has_auth_tuple(struct vlr_subscr *vsub,
+ int max_reuse_count)
+{
+ return _vlr_subscr_next_auth_tuple(vsub, max_reuse_count) != NULL;
+}
+
+static bool check_auth_resp(struct vlr_subscr *vsub, bool is_r99,
+ bool is_utran, const uint8_t *res,
+ uint8_t res_len)
+{
+ struct vlr_auth_tuple *at = vsub->last_tuple;
+ struct osmo_auth_vector *vec = &at->vec;
+ bool check_umts;
+ bool res_is_umts_aka;
+ OSMO_ASSERT(at);
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "AUTH on %s received %s: %s (%u bytes)\n",
+ is_utran ? "UTRAN" : "GERAN",
+ is_utran ? "RES" : "SRES/RES",
+ osmo_hexdump_nospc(res, res_len), res_len);
+
+ /* RES must be present and at least 32bit */
+ if (!res || !res_len) {
+ LOGVSUBP(LOGL_NOTICE, vsub, "AUTH SRES/RES missing\n");
+ goto out_false;
+ }
+
+ /* We're deciding the UMTS AKA-ness of the response by the RES size. So let's make sure we can't
+ * mix them up by size. On UTRAN, we expect full length RES always, no way to mix up there. */
+ if (!is_utran && vec->res_len == sizeof(vec->sres))
+ LOGVSUBP(LOGL_ERROR, vsub, "Unforeseen situation: UMTS AKA's RES length"
+ " equals the size of SRES: %u -- this code wants to differentiate"
+ " the two by their size, which won't work properly now.\n", vec->res_len);
+
+ /* RES must be either vec->res_len (UMTS AKA) or sizeof(sres) (GSM AKA) */
+ if (res_len == vec->res_len)
+ res_is_umts_aka = true;
+ else if (res_len == sizeof(vec->sres))
+ res_is_umts_aka = false;
+ else {
+ if (is_utran)
+ LOGVSUBP(LOGL_NOTICE, vsub, "AUTH RES has invalid length: %u."
+ " Expected %u (UMTS AKA)\n",
+ res_len, vec->res_len);
+ else
+ LOGVSUBP(LOGL_NOTICE, vsub, "AUTH SRES/RES has invalid length: %u."
+ " Expected either %zu (GSM AKA) or %u (UMTS AKA)\n",
+ res_len, sizeof(vec->sres), vec->res_len);
+ goto out_false;
+ }
+
+ check_umts = (is_r99
+ && (vec->auth_types & OSMO_AUTH_TYPE_UMTS)
+ && res_is_umts_aka);
+
+ /* Even on an R99 capable MS with a UMTS AKA capable USIM,
+ * the MS may still choose to only perform GSM AKA, as
+ * long as the bearer is GERAN -- never on UTRAN: */
+ if (is_utran && !check_umts) {
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "AUTH via UTRAN, cannot allow GSM AKA"
+ " (MS is %sR99 capable, vec has %sUMTS AKA tokens, res_len=%u is %s)\n",
+ is_r99 ? "" : "NOT ",
+ (vec->auth_types & OSMO_AUTH_TYPE_UMTS) ? "" : "NO ",
+ res_len, (res_len == vec->res_len)? "valid" : "INVALID on UTRAN");
+ goto out_false;
+ }
+
+ if (check_umts) {
+ if (res_len != vec->res_len
+ || memcmp(res, vec->res, res_len)) {
+ LOGVSUBP(LOGL_INFO, vsub, "UMTS AUTH failure:"
+ " mismatching res (expected res=%s)\n",
+ osmo_hexdump(vec->res, vec->res_len));
+ goto out_false;
+ }
+
+ LOGVSUBP(LOGL_INFO, vsub, "AUTH established UMTS security"
+ " context\n");
+ vsub->sec_ctx = VLR_SEC_CTX_UMTS;
+ return true;
+ } else {
+ if (res_len != sizeof(vec->sres)
+ || memcmp(res, vec->sres, sizeof(vec->sres))) {
+ LOGVSUBP(LOGL_INFO, vsub, "GSM AUTH failure:"
+ " mismatching sres (expected sres=%s)\n",
+ osmo_hexdump(vec->sres, sizeof(vec->sres)));
+ goto out_false;
+ }
+
+ LOGVSUBP(LOGL_INFO, vsub, "AUTH established GSM security"
+ " context\n");
+ vsub->sec_ctx = VLR_SEC_CTX_GSM;
+ return true;
+ }
+
+out_false:
+ vsub->sec_ctx = VLR_SEC_CTX_NONE;
+ return false;
+}
+
+static void auth_fsm_onenter_failed(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+
+ /* If authentication hasn't even started, e.g. the HLR sent no auth
+ * info, then we also don't need to tell the HLR about an auth failure.
+ */
+ if (afp->auth_requested) {
+ int rc = vlr_subscr_tx_auth_fail_rep(vsub);
+ if (rc < 0)
+ LOGVSUBP(LOGL_ERROR, vsub, "Failed to communicate AUTH failure to HLR\n");
+ }
+}
+
+enum auth_fsm_result {
+ /* Authentication verified the subscriber. */
+ AUTH_FSM_PASSED = 0,
+ /* HLR does not have authentication info for this subscriber. */
+ AUTH_FSM_NO_AUTH_INFO,
+ /* Authentication was attempted but failed. */
+ AUTH_FSM_FAILURE,
+};
+
+const char *auth_fsm_result_str[] = {
+ [AUTH_FSM_PASSED] = "PASSED",
+ [AUTH_FSM_NO_AUTH_INFO] = "NO_AUTH_INFO",
+ [AUTH_FSM_FAILURE] = "FAILURE",
+};
+
+/* Terminate the Auth FSM Instance and notify parent */
+static void auth_fsm_term(struct osmo_fsm_inst *fi, enum auth_fsm_result result, enum gsm48_reject_value cause)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+
+ LOGPFSM(fi, "Authentication terminating with result %s%s%s\n",
+ auth_fsm_result_str[result],
+ cause ? ", cause " : "",
+ cause ? gsm48_reject_value_name(cause) : "");
+
+ /* Do one final state transition (mostly for logging purpose)
+ * and set the parent_term_event according to result */
+ switch (result) {
+ case AUTH_FSM_PASSED:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTHENTICATED, 0, 0);
+ fi->proc.parent_term_event = afp->parent_event_success;
+ break;
+ case AUTH_FSM_NO_AUTH_INFO:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0);
+ fi->proc.parent_term_event = afp->parent_event_no_auth_info;
+ break;
+ case AUTH_FSM_FAILURE:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0);
+ fi->proc.parent_term_event = afp->parent_event_failure;
+ break;
+ }
+
+ /* return the result to the parent FSM */
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &cause);
+}
+
+static void auth_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ vsub->auth_fsm = NULL;
+}
+
+/* back-end function transmitting authentication. Caller ensures we have valid
+ * tuple */
+static int _vlr_subscr_authenticate(struct osmo_fsm_inst *fi)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct vlr_auth_tuple *at;
+ bool use_umts_aka;
+
+ /* Caller ensures we have vectors available */
+ at = vlr_subscr_get_auth_tuple(vsub, afp->auth_tuple_max_reuse_count);
+ if (!at) {
+ LOGPFSML(fi, LOGL_ERROR, "A previous check ensured that an"
+ " auth tuple was available, but now there is in fact"
+ " none.\n");
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_NETWORK_FAILURE);
+ return -1;
+ }
+
+ use_umts_aka = vlr_use_umts_aka(&at->vec, afp->is_r99);
+ LOGPFSM(fi, "got auth tuple: use_count=%d key_seq=%d"
+ " -- will use %s AKA (is_r99=%s, at->vec.auth_types=0x%x)\n",
+ at->use_count, at->key_seq,
+ use_umts_aka ? "UMTS" : "GSM", afp->is_r99 ? "yes" : "no", at->vec.auth_types);
+
+ /* Transmit auth req to subscriber */
+ afp->auth_requested = true;
+ vsub->last_tuple = at;
+ vsub->vlr->ops.tx_auth_req(vsub->msc_conn_ref, at, use_umts_aka);
+ return 0;
+}
+
+/***********************************************************************
+ * FSM State Action functions
+ ***********************************************************************/
+
+/* Initial State of TS 23.018 AUT_VLR */
+static void auth_fsm_needs_auth(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+
+ OSMO_ASSERT(event == VLR_AUTH_E_START);
+
+ /* Start off with the default max_reuse_count, possibly change that if we
+ * need to re-use an old tuple. */
+ afp->auth_tuple_max_reuse_count = vsub->vlr->cfg.auth_tuple_max_reuse_count;
+
+ /* Check if we have vectors available */
+ if (!vlr_subscr_has_auth_tuple(vsub, afp->auth_tuple_max_reuse_count)) {
+ /* Obtain_Authentication_Sets_VLR */
+ int rc = vlr_subscr_req_sai(vsub, NULL, NULL);
+ if (rc < 0)
+ LOGPFSM(fi, "Failed to request Authentication Sets from VLR\n");
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_NEEDS_AUTH_WAIT_AI,
+ GSM_29002_TIMER_M, 0);
+ } else {
+ /* go straight ahead with sending auth request */
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP, auth_fsm_state_tdef, vlr_tdefs, -1);
+ _vlr_subscr_authenticate(fi);
+ }
+}
+
+/* Waiting for Authentication Info from HLR */
+static void auth_fsm_wait_ai(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct osmo_gsup_message *gsup = data;
+
+ if (event == VLR_AUTH_E_HLR_SAI_NACK)
+ LOGPFSM(fi, "GSUP: rx Auth Info Error cause: %d: %s\n",
+ gsup->cause,
+ get_value_string(gsm48_gmm_cause_names, gsup->cause));
+
+ /* We are in what corresponds to the
+ * Wait_For_Authentication_Sets state of TS 23.018 OAS_VLR */
+ if ((event == VLR_AUTH_E_HLR_SAI_ACK && !gsup->num_auth_vectors)
+ || (event == VLR_AUTH_E_HLR_SAI_NACK &&
+ gsup->cause != GMM_CAUSE_IMSI_UNKNOWN)
+ || (event == VLR_AUTH_E_HLR_SAI_ABORT)) {
+ if (vsub->vlr->cfg.auth_reuse_old_sets_on_error
+ && vlr_subscr_has_auth_tuple(vsub, -1)) {
+ /* To re-use an old tuple, disable the max_reuse_count
+ * constraint. */
+ afp->auth_tuple_max_reuse_count = -1;
+ goto pass;
+ }
+ }
+
+ switch (event) {
+ case VLR_AUTH_E_HLR_SAI_ACK:
+ if (!gsup->num_auth_vectors) {
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+ vlr_subscr_update_tuples(vsub, gsup);
+ goto pass;
+ case VLR_AUTH_E_HLR_SAI_NACK:
+ /* HLR did not return Auth Info, hence cannot authenticate. (The caller may still decide to permit
+ * attaching without authentication) */
+ auth_fsm_term(fi, AUTH_FSM_NO_AUTH_INFO, vlr_gmm_cause_to_reject_cause_domain(gsup->cause, true));
+ break;
+ case VLR_AUTH_E_HLR_SAI_ABORT:
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, vlr_gmm_cause_to_reject_cause_domain(gsup->cause, true));
+ break;
+ }
+
+ return;
+pass:
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP, auth_fsm_state_tdef, vlr_tdefs, -1);
+
+ _vlr_subscr_authenticate(fi);
+}
+
+/* Waiting for Authentication Response from MS */
+static void auth_fsm_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+ struct vlr_auth_resp_par *par = data;
+ int rc;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_AUTH_RESP:
+ rc = check_auth_resp(vsub, par->is_r99, par->is_utran,
+ par->res, par->res_len);
+ if (rc == false) {
+ if (!afp->by_imsi) {
+ vlr->ops.tx_id_req(vsub->msc_conn_ref,
+ GSM_MI_TYPE_IMSI);
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_ID_IMSI, auth_fsm_state_tdef, vlr_tdefs, -1);
+ } else {
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_ILLEGAL_MS);
+ }
+ } else {
+ auth_fsm_term(fi, AUTH_FSM_PASSED, 0);
+ }
+ break;
+ case VLR_AUTH_E_MS_AUTH_FAIL:
+ if (par->auts) {
+ /* First failure, start re-sync attempt */
+ rc = vlr_subscr_req_sai(vsub, par->auts,
+ vsub->last_tuple->vec.rand);
+ osmo_fsm_inst_state_chg(fi,
+ VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC,
+ GSM_29002_TIMER_M, 0);
+ } else
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_ILLEGAL_MS);
+ break;
+ }
+}
+
+/* Waiting for Authentication Info from HLR (resync case) */
+static void auth_fsm_wait_ai_resync(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct osmo_gsup_message *gsup = data;
+
+ /* We are in what corresponds to the
+ * Wait_For_Authentication_Sets state of TS 23.018 OAS_VLR */
+ if ((event == VLR_AUTH_E_HLR_SAI_ACK && !gsup->num_auth_vectors) ||
+ (event == VLR_AUTH_E_HLR_SAI_NACK &&
+ gsup->cause != GMM_CAUSE_IMSI_UNKNOWN) ||
+ (event == VLR_AUTH_E_HLR_SAI_ABORT)) {
+ /* result = procedure error */
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_NETWORK_FAILURE);
+ }
+ switch (event) {
+ case VLR_AUTH_E_HLR_SAI_ACK:
+ vlr_subscr_update_tuples(vsub, gsup);
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP_RESYNC, auth_fsm_state_tdef, vlr_tdefs, -1);
+ _vlr_subscr_authenticate(fi);
+ break;
+ case VLR_AUTH_E_HLR_SAI_NACK:
+ auth_fsm_term(fi,
+ AUTH_FSM_FAILURE,
+ gsup->cause == GMM_CAUSE_IMSI_UNKNOWN ?
+ GSM48_REJECT_IMSI_UNKNOWN_IN_HLR
+ : GSM48_REJECT_NETWORK_FAILURE);
+ break;
+ }
+}
+
+/* Waiting for AUTH RESP from MS (re-sync case) */
+static void auth_fsm_wait_auth_resp_resync(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct vlr_auth_resp_par *par = data;
+ struct vlr_instance *vlr = vsub->vlr;
+ int rc;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_AUTH_RESP:
+ rc = check_auth_resp(vsub, par->is_r99, par->is_utran,
+ par->res, par->res_len);
+ if (rc == false) {
+ if (!afp->by_imsi) {
+ vlr->ops.tx_id_req(vsub->msc_conn_ref,
+ GSM_MI_TYPE_IMSI);
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_ID_IMSI, auth_fsm_state_tdef, vlr_tdefs, -1);
+ } else {
+ /* Result = Aborted */
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_SYNCH_FAILURE);
+ }
+ } else {
+ /* Result = Pass */
+ auth_fsm_term(fi, AUTH_FSM_PASSED, 0);
+ }
+ break;
+ case VLR_AUTH_E_MS_AUTH_FAIL:
+ /* Second failure: Result = Fail */
+ auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_SYNCH_FAILURE);
+ break;
+ }
+}
+
+/* AUT_VLR waiting for Obtain_IMSI_VLR result */
+static void auth_fsm_wait_imsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ const char *mi_string = data;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_ID_IMSI:
+ if (vsub->imsi[0]
+ && !vlr_subscr_matches_imsi(vsub, mi_string)) {
+ LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP differs:"
+ " %s\n", mi_string);
+ } else {
+ strncpy(vsub->imsi, mi_string, sizeof(vsub->imsi));
+ vsub->imsi[sizeof(vsub->imsi)-1] = '\0';
+ }
+ /* retry with identity=IMSI */
+ afp->by_imsi = true;
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_NEEDS_AUTH, 0, 0);
+ osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state auth_fsm_states[] = {
+ [VLR_SUB_AS_NEEDS_AUTH] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH),
+ .in_event_mask = S(VLR_AUTH_E_START),
+ .out_state_mask = S(VLR_SUB_AS_NEEDS_AUTH_WAIT_AI) |
+ S(VLR_SUB_AS_WAIT_RESP),
+ .action = auth_fsm_needs_auth,
+ },
+ [VLR_SUB_AS_NEEDS_AUTH_WAIT_AI] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH_WAIT_AI),
+ .in_event_mask = S(VLR_AUTH_E_HLR_SAI_ACK) |
+ S(VLR_AUTH_E_HLR_SAI_NACK),
+ .out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_WAIT_RESP),
+ .action = auth_fsm_wait_ai,
+ },
+ [VLR_SUB_AS_WAIT_RESP] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_RESP),
+ .in_event_mask = S(VLR_AUTH_E_MS_AUTH_RESP) |
+ S(VLR_AUTH_E_MS_AUTH_FAIL),
+ .out_state_mask = S(VLR_SUB_AS_WAIT_ID_IMSI) |
+ S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_AUTHENTICATED) |
+ S(VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC),
+ .action = auth_fsm_wait_auth_resp,
+ },
+ [VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC),
+ .in_event_mask = S(VLR_AUTH_E_HLR_SAI_ACK) |
+ S(VLR_AUTH_E_HLR_SAI_NACK),
+ .out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_WAIT_RESP_RESYNC),
+ .action = auth_fsm_wait_ai_resync,
+ },
+ [VLR_SUB_AS_WAIT_RESP_RESYNC] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_RESP_RESYNC),
+ .in_event_mask = S(VLR_AUTH_E_MS_AUTH_RESP) |
+ S(VLR_AUTH_E_MS_AUTH_FAIL),
+ .out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_AUTHENTICATED),
+ .action = auth_fsm_wait_auth_resp_resync,
+ },
+ [VLR_SUB_AS_WAIT_ID_IMSI] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_ID_IMSI),
+ .in_event_mask = S(VLR_AUTH_E_MS_ID_IMSI),
+ .out_state_mask = S(VLR_SUB_AS_NEEDS_AUTH),
+ .action = auth_fsm_wait_imsi,
+ },
+ [VLR_SUB_AS_AUTHENTICATED] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_AUTHENTICATED),
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ },
+ [VLR_SUB_AS_AUTH_FAILED] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_AUTH_FAILED),
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .onenter = auth_fsm_onenter_failed,
+ },
+};
+
+static struct osmo_fsm vlr_auth_fsm = {
+ .name = "VLR_Authenticate",
+ .states = auth_fsm_states,
+ .num_states = ARRAY_SIZE(auth_fsm_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DLGLOBAL,
+ .event_names = fsm_auth_event_names,
+ .cleanup = auth_fsm_cleanup,
+};
+
+void vlr_auth_fsm_init(bool is_ps)
+{
+ if (is_ps)
+ auth_fsm_state_tdef = sgsn_auth_tdef_states;
+ else
+ auth_fsm_state_tdef = msc_auth_tdef_states;
+
+ OSMO_ASSERT(osmo_fsm_register(&vlr_auth_fsm) == 0);
+}
+
+void vlr_auth_fsm_set_log_subsys(int log_level)
+{
+ vlr_auth_fsm.log_subsys = log_level;
+}
+
+/***********************************************************************
+ * User API (for SGSN/MSC code)
+ ***********************************************************************/
+
+/* MSC->VLR: Start Procedure Authenticate_VLR (TS 23.012 Ch. 4.1.2.2) */
+struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub,
+ struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_no_auth_info,
+ uint32_t parent_event_failure,
+ bool is_r99,
+ bool is_utran)
+{
+ struct osmo_fsm_inst *fi;
+ struct auth_fsm_priv *afp;
+
+ fi = osmo_fsm_inst_alloc_child(&vlr_auth_fsm, parent, parent_event_failure);
+ if (!fi) {
+ osmo_fsm_inst_dispatch(parent, parent_event_failure, 0);
+ return NULL;
+ }
+
+ afp = talloc_zero(fi, struct auth_fsm_priv);
+ if (!afp) {
+ osmo_fsm_inst_dispatch(parent, parent_event_failure, 0);
+ return NULL;
+ }
+
+ afp->vsub = vsub;
+ if (vsub->imsi[0])
+ afp->by_imsi = true;
+ afp->is_r99 = is_r99;
+ afp->is_utran = is_utran;
+ afp->parent_event_success = parent_event_success;
+ afp->parent_event_no_auth_info = parent_event_no_auth_info;
+ afp->parent_event_failure = parent_event_failure;
+ fi->priv = afp;
+ vsub->auth_fsm = fi;
+
+ osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
+
+ return fi;
+}
+
+bool auth_try_reuse_tuple(struct vlr_subscr *vsub, uint8_t key_seq)
+{
+ int max_reuse_count = vsub->vlr->cfg.auth_tuple_max_reuse_count;
+ struct vlr_auth_tuple *at = vsub->last_tuple;
+
+ if (!at)
+ return false;
+ if ((max_reuse_count >= 0) && (at->use_count > max_reuse_count))
+ return false;
+ if (at->key_seq != key_seq)
+ return false;
+ at->use_count++;
+ return true;
+}
+
diff --git a/src/libvlr/vlr_auth_fsm.h b/src/libvlr/vlr_auth_fsm.h
new file mode 100644
index 0000000..1cb25b6
--- /dev/null
+++ b/src/libvlr/vlr_auth_fsm.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <osmocom/core/utils.h>
+
+struct vlr_subscr;
+
+/* Parameters to VLR_AUTH_E_MS_AUTH_RESP */
+struct vlr_auth_resp_par {
+ bool is_r99;
+ bool is_utran;
+ const uint8_t *res;
+ unsigned int res_len;
+ const uint8_t *auts;
+};
+
+enum vlr_fsm_auth_event {
+ VLR_AUTH_E_START,
+ /* TS 23.018 OAS_VLR1(2): SendAuthInfo ACK from HLR */
+ VLR_AUTH_E_HLR_SAI_ACK,
+ /* TS 23.018 OAS_VLR1(2): SendAuthInfo NACK from HLR */
+ VLR_AUTH_E_HLR_SAI_NACK,
+ /* FIXME: merge with NACK? */
+ VLR_AUTH_E_HLR_SAI_ABORT,
+ /* Authentication Response from MS */
+ VLR_AUTH_E_MS_AUTH_RESP,
+ /* Authentication Failure from MS */
+ VLR_AUTH_E_MS_AUTH_FAIL,
+ /* Identity Response (IMSI) from MS */
+ VLR_AUTH_E_MS_ID_IMSI,
+};
+
+struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub,
+ struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_no_auth_info,
+ uint32_t parent_event_failure,
+ bool is_r99,
+ bool is_utran);
+
+void vlr_auth_fsm_init(bool is_ps);
+void vlr_auth_fsm_set_log_subsys(int log_level);
+bool auth_try_reuse_tuple(struct vlr_subscr *vsub, uint8_t key_seq);
diff --git a/src/libvlr/vlr_core.h b/src/libvlr/vlr_core.h
new file mode 100644
index 0000000..59c2cd1
--- /dev/null
+++ b/src/libvlr/vlr_core.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <osmocom/vlr/vlr.h>
+
+struct osmo_gsup_message;
+
+int vlr_subscr_req_lu(struct vlr_subscr *vsub) __attribute__((warn_unused_result));
+int vlr_subscr_req_sai(struct vlr_subscr *vsub, const uint8_t *auts,
+ const uint8_t *auts_rand) __attribute__((warn_unused_result));
+int vlr_subscr_tx_req_check_imei(const struct vlr_subscr *vsub);
+void vlr_subscr_update_tuples(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup);
+
+/* Logging */
+extern int g_vlr_log_cat[_OSMO_VLR_LOGC_MAX];
+
+#define LOGVLR(lvl, fmt, args...) LOGP(g_vlr_log_cat[OSMO_VLR_LOGC_VLR], lvl, fmt, ## args)
+#define LOGSGS(lvl, fmt, args...) LOGP(g_vlr_log_cat[OSMO_VLR_LOGC_SGS], lvl, fmt, ## args)
+
+#define LOGGSUPP(level, gsup, fmt, args...) \
+ LOGVLR(level, "GSUP(%s) " fmt, (gsup)->imsi, ## args)
+
+#define LOGVSUBP(level, vsub, fmt, args...) \
+ LOGVLR(level, "SUBSCR(%s) " fmt, vlr_subscr_name(vsub), ## args)
+
diff --git a/src/libvlr/vlr_logging.c b/src/libvlr/vlr_logging.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/libvlr/vlr_logging.c
diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c
new file mode 100644
index 0000000..4bbbbbe
--- /dev/null
+++ b/src/libvlr/vlr_lu_fsm.c
@@ -0,0 +1,1850 @@
+/* Osmocom Visitor Location Register (VLR): Location Update FSMs */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/vlr/vlr.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+#include "vlr_lu_fsm.h"
+#include "vlr_sgs_fsm.h"
+
+#define S(x) (1 << (x))
+
+#define LU_TIMEOUT_LONG 30
+
+enum vlr_fsm_result {
+ VLR_FSM_RESULT_NONE,
+ VLR_FSM_RESULT_SUCCESS,
+ VLR_FSM_RESULT_FAILURE,
+};
+
+
+/***********************************************************************
+ * Update_HLR_VLR, TS 23.012 Chapter 4.1.2.4
+ ***********************************************************************/
+
+enum upd_hlr_vlr_state {
+ UPD_HLR_VLR_S_INIT,
+ UPD_HLR_VLR_S_WAIT_FOR_DATA,
+ UPD_HLR_VLR_S_DONE,
+};
+
+enum upd_hlr_vlr_evt {
+ UPD_HLR_VLR_E_START,
+ UPD_HLR_VLR_E_INS_SUB_DATA,
+ UPD_HLR_VLR_E_ACT_TRACE_MODE,
+ UPD_HLR_VLR_E_FW_CHECK_SS_IND,
+ UPD_HLR_VLR_E_UPD_LOC_ACK,
+ UPD_HLR_VLR_E_UPD_LOC_NACK,
+};
+
+static const struct value_string upd_hlr_vlr_event_names[] = {
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_START),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_INS_SUB_DATA),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_ACT_TRACE_MODE),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_FW_CHECK_SS_IND),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_UPD_LOC_ACK),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_UPD_LOC_NACK),
+ { 0, NULL }
+};
+
+static inline struct vlr_subscr *upd_hlr_vlr_fi_priv(struct osmo_fsm_inst *fi);
+
+static void upd_hlr_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = upd_hlr_vlr_fi_priv(fi);
+ int rc;
+
+ OSMO_ASSERT(event == UPD_HLR_VLR_E_START);
+
+ /* Send UpdateLocation to HLR */
+ rc = vlr_subscr_req_lu(vsub);
+ if (rc < 0)
+ LOGPFSML(fi, LOGL_ERROR, "Failed to send UpdateLocation to HLR\n");
+ osmo_fsm_inst_state_chg(fi, UPD_HLR_VLR_S_WAIT_FOR_DATA,
+ LU_TIMEOUT_LONG, 0);
+}
+
+static void upd_hlr_vlr_fsm_wait_data(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = upd_hlr_vlr_fi_priv(fi);
+
+ switch (event) {
+ case UPD_HLR_VLR_E_INS_SUB_DATA:
+ /* FIXME: Insert_Subscr_Data_VLR */
+ break;
+ case UPD_HLR_VLR_E_ACT_TRACE_MODE:
+ /* TODO: Activate_Tracing_VLR */
+ break;
+ case UPD_HLR_VLR_E_FW_CHECK_SS_IND:
+ /* TODO: Forward Check SS Ind to MSC */
+ break;
+ case UPD_HLR_VLR_E_UPD_LOC_ACK:
+ /* Inside Update_HLR_VLR after UpdateLocationAck */
+ vsub->sub_dataconf_by_hlr_ind = true;
+ vsub->loc_conf_in_hlr_ind = true;
+ osmo_fsm_inst_state_chg(fi, UPD_HLR_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ case UPD_HLR_VLR_E_UPD_LOC_NACK:
+ /* Inside Update_HLR_VLR after UpdateLocationNack */
+ /* TODO: Check_User_Error_In_Serving_Network_Entity */
+ vsub->sub_dataconf_by_hlr_ind = false;
+ vsub->loc_conf_in_hlr_ind = false;
+ osmo_fsm_inst_state_chg(fi, UPD_HLR_VLR_S_DONE, 0, 0);
+ /* Data is a pointer to a gsm48_gmm_cause which we
+ * simply pass through */
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state upd_hlr_vlr_states[] = {
+ [UPD_HLR_VLR_S_INIT] = {
+ .in_event_mask = S(UPD_HLR_VLR_E_START),
+ .out_state_mask = S(UPD_HLR_VLR_S_WAIT_FOR_DATA),
+ .name = OSMO_STRINGIFY(UPD_HLR_VLR_S_INIT),
+ .action = upd_hlr_vlr_fsm_init,
+ },
+ [UPD_HLR_VLR_S_WAIT_FOR_DATA] = {
+ .in_event_mask = S(UPD_HLR_VLR_E_INS_SUB_DATA) |
+ S(UPD_HLR_VLR_E_ACT_TRACE_MODE) |
+ S(UPD_HLR_VLR_E_FW_CHECK_SS_IND) |
+ S(UPD_HLR_VLR_E_UPD_LOC_ACK) |
+ S(UPD_HLR_VLR_E_UPD_LOC_NACK),
+ .out_state_mask = S(UPD_HLR_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(UPD_HLR_VLR_S_WAIT_FOR_DATA),
+ .action = upd_hlr_vlr_fsm_wait_data,
+ },
+ [UPD_HLR_VLR_S_DONE] = {
+ .name = OSMO_STRINGIFY(UPD_HLR_VLR_S_DONE),
+ },
+};
+
+static struct osmo_fsm upd_hlr_vlr_fsm = {
+ .name = "upd_hlr_vlr_fsm",
+ .states = upd_hlr_vlr_states,
+ .num_states = ARRAY_SIZE(upd_hlr_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DLGLOBAL,
+ .event_names = upd_hlr_vlr_event_names,
+};
+
+static inline struct vlr_subscr *upd_hlr_vlr_fi_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi->fsm == &upd_hlr_vlr_fsm);
+ return (struct vlr_subscr*)fi->priv;
+}
+
+struct osmo_fsm_inst *
+upd_hlr_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t parent_event)
+{
+ struct osmo_fsm_inst *fi;
+
+ fi = osmo_fsm_inst_alloc_child(&upd_hlr_vlr_fsm, parent,
+ parent_event);
+ if (!fi)
+ return NULL;
+
+ fi->priv = vsub;
+ osmo_fsm_inst_dispatch(fi, UPD_HLR_VLR_E_START, NULL);
+
+ return fi;
+}
+
+
+/***********************************************************************
+ * Subscriber_Present_VLR, TS 29.002 Chapter 25.10.1
+ ***********************************************************************/
+
+enum sub_pres_vlr_state {
+ SUB_PRES_VLR_S_INIT,
+ SUB_PRES_VLR_S_WAIT_FOR_HLR,
+ SUB_PRES_VLR_S_DONE,
+};
+
+enum sub_pres_vlr_event {
+ SUB_PRES_VLR_E_START,
+ SUB_PRES_VLR_E_READY_SM_CNF,
+ SUB_PRES_VLR_E_READY_SM_ERR,
+};
+
+static const struct value_string sub_pres_vlr_event_names[] = {
+ OSMO_VALUE_STRING(SUB_PRES_VLR_E_START),
+ OSMO_VALUE_STRING(SUB_PRES_VLR_E_READY_SM_CNF),
+ OSMO_VALUE_STRING(SUB_PRES_VLR_E_READY_SM_ERR),
+ { 0, NULL }
+};
+
+static inline struct vlr_subscr *sub_pres_vlr_fi_priv(struct osmo_fsm_inst *fi);
+
+static void sub_pres_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = sub_pres_vlr_fi_priv(fi);
+ OSMO_ASSERT(event == SUB_PRES_VLR_E_START);
+
+ if (!vsub->ms_not_reachable_flag) {
+ osmo_fsm_inst_state_chg(fi, SUB_PRES_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return;
+ }
+ /* FIXME: Send READY_FOR_SM via GSUP */
+ osmo_fsm_inst_state_chg(fi, SUB_PRES_VLR_S_WAIT_FOR_HLR,
+ LU_TIMEOUT_LONG, 0);
+}
+
+static void sub_pres_vlr_fsm_wait_hlr(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = sub_pres_vlr_fi_priv(fi);
+
+ switch (event) {
+ case SUB_PRES_VLR_E_READY_SM_CNF:
+ vsub->ms_not_reachable_flag = false;
+ break;
+ case SUB_PRES_VLR_E_READY_SM_ERR:
+ break;
+ }
+ osmo_fsm_inst_state_chg(fi, SUB_PRES_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static const struct osmo_fsm_state sub_pres_vlr_states[] = {
+ [SUB_PRES_VLR_S_INIT] = {
+ .in_event_mask = S(SUB_PRES_VLR_E_START),
+ .out_state_mask = S(SUB_PRES_VLR_S_WAIT_FOR_HLR) |
+ S(SUB_PRES_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(SUB_PRES_VLR_S_INIT),
+ .action = sub_pres_vlr_fsm_init,
+ },
+ [SUB_PRES_VLR_S_WAIT_FOR_HLR] = {
+ .in_event_mask = S(SUB_PRES_VLR_E_READY_SM_CNF) |
+ S(SUB_PRES_VLR_E_READY_SM_ERR),
+ .out_state_mask = S(SUB_PRES_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(SUB_PRES_VLR_S_WAIT_FOR_HLR),
+ .action = sub_pres_vlr_fsm_wait_hlr,
+ },
+ [SUB_PRES_VLR_S_DONE] = {
+ .name = OSMO_STRINGIFY(SUB_PRES_VLR_S_DONE),
+ },
+};
+
+static struct osmo_fsm sub_pres_vlr_fsm = {
+ .name = "sub_pres_vlr_fsm",
+ .states = sub_pres_vlr_states,
+ .num_states = ARRAY_SIZE(sub_pres_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DLGLOBAL,
+ .event_names = sub_pres_vlr_event_names,
+};
+
+static inline struct vlr_subscr *sub_pres_vlr_fi_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi->fsm == &sub_pres_vlr_fsm);
+ return (struct vlr_subscr*)fi->priv;
+}
+
+/* THIS IS CURRENTLY DEAD CODE, SINCE WE NEVER SET vsub->ms_not_reachable_flag = true.
+ *
+ * Note that the start event is dispatched right away, so in case the FSM immediately concludes from that
+ * event, the created FSM struct may no longer be valid as it already deallocated again, and it may
+ * furthermore already have invoked the parent FSM instance's deallocation as well. Hence, instead of
+ * returning, store the created FSM instance address in *fi_p before dispatching the event. It is thus
+ * possible to store the instance's pointer in a parent FSM instance without running danger of using
+ * already freed memory. */
+void sub_pres_vlr_fsm_start(struct osmo_fsm_inst **fi_p,
+ struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t term_event)
+{
+ struct osmo_fsm_inst *fi;
+
+ OSMO_ASSERT(fi_p);
+
+ fi = osmo_fsm_inst_alloc_child(&sub_pres_vlr_fsm, parent,
+ term_event);
+ *fi_p = fi;
+ if (!fi)
+ return;
+
+ fi->priv = vsub;
+ osmo_fsm_inst_dispatch(fi, SUB_PRES_VLR_E_START, NULL);
+}
+
+/***********************************************************************
+ * Location_Update_Completion_VLR, TS 23.012 Chapter 4.1.2.3
+ ***********************************************************************/
+
+enum lu_compl_vlr_state {
+ LU_COMPL_VLR_S_INIT,
+ LU_COMPL_VLR_S_WAIT_SUB_PRES,
+ LU_COMPL_VLR_S_WAIT_IMEI,
+ LU_COMPL_VLR_S_WAIT_IMEI_TMSI,
+ LU_COMPL_VLR_S_WAIT_TMSI_CNF,
+ LU_COMPL_VLR_S_DONE,
+};
+
+enum lu_compl_vlr_event {
+ LU_COMPL_VLR_E_START,
+ LU_COMPL_VLR_E_SUB_PRES_COMPL,
+ LU_COMPL_VLR_E_IMEI_CHECK_ACK,
+ LU_COMPL_VLR_E_IMEI_CHECK_NACK,
+ LU_COMPL_VLR_E_NEW_TMSI_ACK,
+};
+
+static const struct value_string lu_compl_vlr_event_names[] = {
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_START),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_SUB_PRES_COMPL),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_IMEI_CHECK_ACK),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_IMEI_CHECK_NACK),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_NEW_TMSI_ACK),
+ { 0, NULL }
+};
+
+struct osmo_tdef_state_timeout msc_lu_compl_tdef_states[32] = {
+ [LU_COMPL_VLR_S_WAIT_IMEI] = { .T = 3270 },
+ [LU_COMPL_VLR_S_WAIT_IMEI_TMSI] = { .T = 3270 },
+ [LU_COMPL_VLR_S_WAIT_TMSI_CNF] = { .T = 3250 },
+};
+
+struct osmo_tdef_state_timeout sgsn_lu_compl_tdef_states[32] = {
+ [LU_COMPL_VLR_S_WAIT_IMEI] = { .T = 3370 },
+ [LU_COMPL_VLR_S_WAIT_IMEI_TMSI] = { .T = 3370 },
+ [LU_COMPL_VLR_S_WAIT_TMSI_CNF] = { .T = 3350 },
+};
+
+struct osmo_tdef_state_timeout *lu_compl_fsm_state_tdef;
+
+struct lu_compl_vlr_priv {
+ struct vlr_subscr *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *sub_pres_vlr_fsm;
+ uint32_t parent_event_success;
+ uint32_t parent_event_failure;
+ void *parent_event_data;
+ enum vlr_fsm_result result;
+ uint8_t cause;
+ bool assign_tmsi;
+ enum vlr_lu_type lu_type;
+ int N; /*< counter of timeouts */
+};
+
+static inline struct lu_compl_vlr_priv *lu_compl_vlr_fi_priv(struct osmo_fsm_inst *fi);
+
+static void _vlr_lu_compl_fsm_done(struct osmo_fsm_inst *fi,
+ enum vlr_fsm_result result,
+ uint8_t cause)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ lcvp->result = result;
+ lcvp->cause = cause;
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_DONE, 0, 0);
+}
+
+static void vlr_lu_compl_fsm_success(struct osmo_fsm_inst *fi)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_subscr *vsub = lcvp->vsub;
+ if (!vsub->lu_complete) {
+ vsub->lu_complete = true;
+ /* Balanced by vlr_subscr_expire() */
+ vlr_subscr_get(vsub, VSUB_USE_ATTACHED);
+ }
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_SUCCESS, 0);
+ vlr_sgs_fsm_update_id(vsub);
+}
+
+static void vlr_lu_compl_fsm_dispatch_result(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ if (!fi->proc.parent) {
+ LOGPFSML(fi, LOGL_ERROR, "No parent FSM\n");
+ return;
+ }
+ osmo_fsm_inst_dispatch(fi->proc.parent,
+ (lcvp->result == VLR_FSM_RESULT_SUCCESS)
+ ? lcvp->parent_event_success
+ : lcvp->parent_event_failure,
+ &lcvp->cause);
+}
+
+static void lu_compl_vlr_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr;
+ OSMO_ASSERT(vsub);
+ vlr = vsub->vlr;
+ OSMO_ASSERT(vlr);
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_START);
+ lcvp->N = 0;
+
+ /* TODO: National Roaming restrictions? */
+ /* TODO: Roaming restriction due to unsupported feature in subscriber
+ * data? */
+ /* TODO: Regional subscription restriction? */
+ /* TODO: Administrative restriction of subscribres' access feature? */
+ /* TODO: AccessRestrictuionData parameter available? */
+ /* TODO: AccessRestrictionData permits RAT? */
+ /* Node 1 */
+ /* TODO: Autonomous CSG supported in VPLMN and allowed by HPLMN? */
+ /* TODO: Hybrid Cel / CSG Cell */
+ /* Node 2 */
+ vsub->la_allowed = true;
+ vsub->imsi_detached_flag = false;
+ /* Start Subscriber_Present_VLR Procedure */
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_SUB_PRES,
+ LU_TIMEOUT_LONG, 0);
+
+ /* If ms_not_reachable_flag == false, the sub_pres_vlr_fsm will anyway terminate straight away and dispatch
+ * LU_COMPL_VLR_E_SUB_PRES_COMPL to this fi, so we might as well skip that dance here and save some logging. */
+ if (vsub->ms_not_reachable_flag)
+ sub_pres_vlr_fsm_start(&lcvp->sub_pres_vlr_fsm, fi, vsub, LU_COMPL_VLR_E_SUB_PRES_COMPL);
+ else
+ osmo_fsm_inst_dispatch(fi, LU_COMPL_VLR_E_SUB_PRES_COMPL, NULL);
+}
+
+static void lu_compl_vlr_new_tmsi(struct osmo_fsm_inst *fi)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (vlr_subscr_alloc_tmsi(vsub)) {
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_FAILURE, GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
+ return;
+ }
+
+ osmo_tdef_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_TMSI_CNF, lu_compl_fsm_state_tdef, vlr_tdefs, -1);
+
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, vsub->tmsi_new, lcvp->lu_type);
+}
+
+/* After completion of Subscriber_Present_VLR */
+static void lu_compl_vlr_wait_subscr_pres(struct osmo_fsm_inst *fi,
+ uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_SUB_PRES_COMPL);
+
+ lcvp->sub_pres_vlr_fsm = NULL;
+
+ /* TODO: Trace_Subscriber_Activity_VLR */
+
+ /* If imeisv_early is enabled: IMEI already retrieved and checked (vlr_loc_upd_node1_pre), don't do it again. */
+ if (vlr->cfg.check_imei_rqd && !vlr->cfg.retrieve_imeisv_early) {
+ /* Check IMEI VLR */
+ osmo_tdef_fsm_inst_state_chg(fi,
+ lcvp->assign_tmsi ?
+ LU_COMPL_VLR_S_WAIT_IMEI_TMSI
+ : LU_COMPL_VLR_S_WAIT_IMEI,
+ lu_compl_fsm_state_tdef, vlr_tdefs, -1);
+ vlr->ops.tx_id_req(lcvp->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ return;
+ }
+
+ /* Do we need to allocate a TMSI? */
+ if (lcvp->assign_tmsi) {
+ lu_compl_vlr_new_tmsi(fi);
+ return;
+ }
+ /* else, any previously used TMSI is now invalid. */
+ vsub->tmsi = GSM_RESERVED_TMSI;
+
+ /* Location Updating Accept */
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, GSM_RESERVED_TMSI, lcvp->lu_type);
+ vlr_lu_compl_fsm_success(fi);
+}
+
+/* Waiting for completion of CHECK_IMEI_VLR */
+static void lu_compl_vlr_wait_imei(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ switch (event) {
+ case LU_COMPL_VLR_E_IMEI_CHECK_ACK:
+ if (!vsub->imei[0]) {
+ /* Abort: Do nothing */
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_FAILURE, GSM48_REJECT_PROTOCOL_ERROR);
+ return;
+ }
+ /* Pass */
+ break;
+
+ case LU_COMPL_VLR_E_IMEI_CHECK_NACK:
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_FAILURE, GSM48_REJECT_ILLEGAL_ME);
+ /* FIXME: IMEI Check Fail to VLR Application (Detach IMSI VLR) */
+ return;
+ }
+
+ /* IMEI is available. Allocate TMSI if needed. */
+ if (lcvp->assign_tmsi) {
+ if (fi->state != LU_COMPL_VLR_S_WAIT_IMEI_TMSI)
+ LOGPFSML(fi, LOGL_ERROR,
+ "TMSI required, expected to be in state"
+ " LU_COMPL_VLR_S_WAIT_IMEI_TMSI,"
+ " am in %s instead\n",
+ osmo_fsm_state_name(fi->fsm, fi->state));
+ /* Logged an error, continue anyway. */
+
+ lu_compl_vlr_new_tmsi(fi);
+
+ /* Wait for TMSI ack */
+ return;
+ }
+ /* else, any previously used TMSI is now invalid. */
+ vsub->tmsi = GSM_RESERVED_TMSI;
+
+ /* No TMSI needed, accept now. */
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, GSM_RESERVED_TMSI, lcvp->lu_type);
+ vlr_lu_compl_fsm_success(fi);
+}
+
+static inline struct lu_compl_vlr_priv *lu_compl_vlr_fi_priv(struct osmo_fsm_inst *fi);
+
+/* Waiting for TMSI confirmation */
+static void lu_compl_vlr_wait_tmsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_subscr *vsub = lcvp->vsub;
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_NEW_TMSI_ACK);
+
+ if (!vsub || vsub->tmsi_new == GSM_RESERVED_TMSI) {
+ LOGPFSML(fi, LOGL_ERROR, "TMSI Realloc Compl implies that"
+ " the subscriber has a new TMSI allocated, but"
+ " the new TMSI is unset.\n");
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_FAILURE, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ vsub->tmsi = vsub->tmsi_new;
+ vsub->tmsi_new = GSM_RESERVED_TMSI;
+ vsub->vlr->ops.subscr_update(vsub);
+
+ vlr_lu_compl_fsm_success(fi);
+}
+
+static void vlr_lu_compl_fsm_reset_n(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ lcvp->N = 0;
+}
+
+static int lu_compl_vlr_timeout(struct osmo_fsm_inst *fi)
+{
+ struct lu_compl_vlr_priv *lcvp = lu_compl_vlr_fi_priv(fi);
+ struct vlr_instance *vlr = lcvp->vsub->vlr;
+ struct vlr_subscr *vsub = lcvp->vsub;
+
+ /* on cs: terminate the FSM */
+ if (vlr_is_cs(lcvp->vsub->vlr))
+ return 1;
+
+ /* PS: we have to resend the complete message 5x times, before failing */
+ if (++lcvp->N >= 5)
+ return 1;
+
+ LOGPFSML(fi, LOGL_ERROR, "LU Compl timeout T%d / N%d\n", fi->T, lcvp->N);
+
+ switch (fi->state) {
+ case LU_COMPL_VLR_S_WAIT_IMEI:
+ case LU_COMPL_VLR_S_WAIT_IMEI_TMSI:
+ vlr->ops.tx_id_req(lcvp->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ break;
+ case LU_COMPL_VLR_S_WAIT_TMSI_CNF:
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, vsub->tmsi_new, lcvp->lu_type);
+ break;
+ }
+
+ osmo_timer_schedule(&fi->timer, vlr_timer_secs(vlr, fi->T, fi->T), 0);
+
+ return 0;
+}
+
+static const struct osmo_fsm_state lu_compl_vlr_states[] = {
+ [LU_COMPL_VLR_S_INIT] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_START),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE) |
+ S(LU_COMPL_VLR_S_WAIT_SUB_PRES),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_INIT),
+ .action = lu_compl_vlr_init,
+ },
+ [LU_COMPL_VLR_S_WAIT_SUB_PRES] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_SUB_PRES_COMPL),
+ .out_state_mask = S(LU_COMPL_VLR_S_WAIT_IMEI) |
+ S(LU_COMPL_VLR_S_WAIT_IMEI_TMSI) |
+ S(LU_COMPL_VLR_S_WAIT_TMSI_CNF) |
+ S(LU_COMPL_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_SUB_PRES),
+ .action = lu_compl_vlr_wait_subscr_pres,
+ },
+ [LU_COMPL_VLR_S_WAIT_IMEI] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_IMEI_CHECK_ACK) |
+ S(LU_COMPL_VLR_E_IMEI_CHECK_NACK),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_IMEI),
+ .onenter = vlr_lu_compl_fsm_reset_n,
+ .action = lu_compl_vlr_wait_imei,
+ },
+ [LU_COMPL_VLR_S_WAIT_IMEI_TMSI] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_IMEI_CHECK_ACK) |
+ S(LU_COMPL_VLR_E_IMEI_CHECK_NACK),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE) |
+ S(LU_COMPL_VLR_S_WAIT_TMSI_CNF),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_IMEI_TMSI),
+ .onenter = vlr_lu_compl_fsm_reset_n,
+ .action = lu_compl_vlr_wait_imei,
+ },
+ [LU_COMPL_VLR_S_WAIT_TMSI_CNF] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_NEW_TMSI_ACK),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_TMSI_CNF),
+ .onenter = vlr_lu_compl_fsm_reset_n,
+ .action = lu_compl_vlr_wait_tmsi,
+ },
+ [LU_COMPL_VLR_S_DONE] = {
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_DONE),
+ .onenter = vlr_lu_compl_fsm_dispatch_result,
+ },
+};
+
+static struct osmo_fsm lu_compl_vlr_fsm = {
+ .name = "lu_compl_vlr_fsm",
+ .states = lu_compl_vlr_states,
+ .num_states = ARRAY_SIZE(lu_compl_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DLGLOBAL,
+ .event_names = lu_compl_vlr_event_names,
+ .timer_cb = lu_compl_vlr_timeout,
+};
+
+static inline struct lu_compl_vlr_priv *lu_compl_vlr_fi_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi->fsm == &lu_compl_vlr_fsm);
+ return (struct lu_compl_vlr_priv*)fi->priv;
+}
+
+struct osmo_fsm_inst *
+lu_compl_vlr_proc_alloc(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ void *msc_conn_ref,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ bool assign_tmsi,
+ enum vlr_lu_type lu_type)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu_compl_vlr_priv *lcvp;
+
+ fi = osmo_fsm_inst_alloc_child(&lu_compl_vlr_fsm, parent,
+ parent_event_failure);
+ if (!fi)
+ return NULL;
+
+ lcvp = talloc_zero(fi, struct lu_compl_vlr_priv);
+ lcvp->vsub = vsub;
+ lcvp->msc_conn_ref = msc_conn_ref;
+ lcvp->parent_event_success = parent_event_success;
+ lcvp->parent_event_failure = parent_event_failure;
+ lcvp->assign_tmsi = assign_tmsi;
+ lcvp->lu_type = lu_type;
+ fi->priv = lcvp;
+
+ return fi;
+}
+
+
+/***********************************************************************
+ * Update_Location_Area_VLR, TS 23.012 Chapter 4.1.2.1
+ ***********************************************************************/
+
+static const struct value_string fsm_lu_event_names[] = {
+ OSMO_VALUE_STRING(VLR_ULA_E_UPDATE_LA),
+ OSMO_VALUE_STRING(VLR_ULA_E_SEND_ID_ACK),
+ OSMO_VALUE_STRING(VLR_ULA_E_SEND_ID_NACK),
+ OSMO_VALUE_STRING(VLR_ULA_E_AUTH_SUCCESS),
+ OSMO_VALUE_STRING(VLR_ULA_E_AUTH_NO_INFO),
+ OSMO_VALUE_STRING(VLR_ULA_E_AUTH_FAILURE),
+ OSMO_VALUE_STRING(VLR_ULA_E_CIPH_RES),
+ OSMO_VALUE_STRING(VLR_ULA_E_ID_IMSI),
+ OSMO_VALUE_STRING(VLR_ULA_E_ID_IMEI),
+ OSMO_VALUE_STRING(VLR_ULA_E_ID_IMEISV),
+ OSMO_VALUE_STRING(VLR_ULA_E_HLR_IMEI_ACK),
+ OSMO_VALUE_STRING(VLR_ULA_E_HLR_IMEI_NACK),
+ OSMO_VALUE_STRING(VLR_ULA_E_HLR_LU_RES),
+ OSMO_VALUE_STRING(VLR_ULA_E_UPD_HLR_COMPL),
+ OSMO_VALUE_STRING(VLR_ULA_E_LU_COMPL_SUCCESS),
+ OSMO_VALUE_STRING(VLR_ULA_E_LU_COMPL_FAILURE),
+ OSMO_VALUE_STRING(VLR_ULA_E_NEW_TMSI_ACK),
+ { 0, NULL }
+};
+
+struct osmo_tdef_state_timeout msc_lu_tdef_states[32] = {
+ [VLR_ULA_S_WAIT_IMEISV] = { .T = 3270 },
+ [VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY] = { .T = 3270 },
+ [VLR_ULA_S_WAIT_IMSI] = { .T = 3270 },
+};
+
+struct osmo_tdef_state_timeout sgsn_lu_tdef_states[32] = {
+ [VLR_ULA_S_WAIT_IMEISV] = { .T = 3370 },
+ [VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY] = { .T = 3370 },
+ [VLR_ULA_S_WAIT_IMSI] = { .T = 3370 },
+};
+
+struct osmo_tdef_state_timeout *lu_fsm_state_tdef;
+
+struct lu_fsm_priv {
+ struct vlr_instance *vlr;
+ struct vlr_subscr *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *upd_hlr_vlr_fsm;
+ struct osmo_fsm_inst *lu_compl_vlr_fsm;
+ uint32_t parent_event_success;
+ uint32_t parent_event_failure;
+ void *parent_event_data;
+ enum vlr_fsm_result result;
+ uint8_t rej_cause;
+
+ enum vlr_lu_type lu_type;
+ bool lu_by_tmsi;
+ char imsi[16];
+ uint32_t tmsi;
+ struct osmo_location_area_id old_lai;
+ struct osmo_location_area_id new_lai;
+ struct osmo_routing_area_id old_rai;
+ struct osmo_routing_area_id new_rai;
+ bool authentication_required;
+ /* is_ciphering_to_be_attempted: true when any A5/n > 0 are enabled. Ciphering is allowed, always attempt to get Auth Info from
+ * the HLR. */
+ bool is_ciphering_to_be_attempted;
+ /* is_ciphering_required: true when A5/0 is disabled. If we cannot get Auth Info from the HLR, reject the
+ * subscriber. */
+ bool is_ciphering_required;
+ uint8_t key_seq;
+ bool is_r99;
+ bool is_utran;
+ bool assign_tmsi;
+
+ /*! count times timer T timed out */
+ int N;
+};
+
+
+/* Determine if given location area is served by this VLR */
+static bool lai_in_this_vlr(struct vlr_instance *vlr,
+ const struct osmo_location_area_id *lai)
+{
+ /* TODO: VLR needs to keep a locally configured list of LAIs */
+ return true;
+}
+
+/* Return true when authentication should be attempted. */
+static bool try_auth(struct lu_fsm_priv *lfp)
+{
+ /* The cases where the authentication procedure should be used
+ * are defined in 3GPP TS 33.102 */
+ /* For now we use a default value passed in to vlr_lu_fsm(). */
+ return lfp->authentication_required ||
+ (lfp->is_ciphering_to_be_attempted && !auth_try_reuse_tuple(lfp->vsub, lfp->key_seq));
+}
+
+/* Return true when CipherModeCmd / SecurityModeCmd should be attempted. */
+static bool is_cmc_smc_to_be_attempted(struct lu_fsm_priv *lfp)
+{
+ /* UTRAN: always send SecModeCmd, even if ciphering is not required.
+ * GERAN: avoid sending CiphModeCmd if ciphering is not required. */
+ return lfp->is_utran || lfp->is_ciphering_to_be_attempted;
+}
+
+/* Determine if a HLR Update is required */
+static bool hlr_update_needed(struct vlr_subscr *vsub)
+{
+ /* TODO: properly decide this, rather than always assuming we
+ * need to update the HLR. */
+ return true;
+}
+
+static inline struct lu_fsm_priv *lu_fsm_fi_priv(struct osmo_fsm_inst *fi);
+
+static void lu_fsm_dispatch_result(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ if (!fi->proc.parent) {
+ LOGPFSML(fi, LOGL_ERROR, "No parent FSM\n");
+ return;
+ }
+ if (lfp->result == VLR_FSM_RESULT_SUCCESS)
+ fi->proc.parent_term_event = lfp->parent_event_success;
+ else
+ fi->proc.parent_term_event = lfp->parent_event_failure;
+
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, lfp->parent_event_data);
+}
+
+static void _lu_fsm_done(struct osmo_fsm_inst *fi,
+ enum vlr_fsm_result result)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ lfp->result = result;
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+}
+
+static void lu_fsm_success(struct osmo_fsm_inst *fi)
+{
+ _lu_fsm_done(fi, VLR_FSM_RESULT_SUCCESS);
+}
+
+static void lu_fsm_failure(struct osmo_fsm_inst *fi, enum gsm48_reject_value rej_cause)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ lfp->vlr->ops.tx_lu_rej(lfp->msc_conn_ref, rej_cause ? : GSM48_REJECT_NETWORK_FAILURE, lfp->lu_type);
+ _lu_fsm_done(fi, VLR_FSM_RESULT_FAILURE);
+}
+
+static void vlr_loc_upd_start_lu_compl_fsm(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ lfp->lu_compl_vlr_fsm =
+ lu_compl_vlr_proc_alloc(fi, lfp->vsub, lfp->msc_conn_ref,
+ VLR_ULA_E_LU_COMPL_SUCCESS,
+ VLR_ULA_E_LU_COMPL_FAILURE,
+ lfp->assign_tmsi,
+ lfp->lu_type);
+
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm, LU_COMPL_VLR_E_START, NULL);
+}
+
+static void lu_fsm_discard_lu_compl_fsm(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ if (!lfp->lu_compl_vlr_fsm)
+ return;
+ osmo_fsm_inst_term(lfp->lu_compl_vlr_fsm, OSMO_FSM_TERM_PARENT, NULL);
+}
+
+/* 4.1.2.1 Node 4 */
+static void vlr_loc_upd_node_4(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+ bool hlr_unknown = false;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (hlr_unknown) {
+ /* FIXME: Delete subscriber record */
+ /* LU REJ: Roaming not allowed */
+ lu_fsm_failure(fi, GSM48_REJECT_ROAMING_NOT_ALLOWED);
+ return;
+ }
+
+ if (lfp->lu_type == VLR_LU_TYPE_IMSI_ATTACH) {
+ /* Update_HLR_VLR */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_HLR_UPD,
+ LU_TIMEOUT_LONG, 0);
+ lfp->upd_hlr_vlr_fsm =
+ upd_hlr_vlr_proc_start(fi, vsub, VLR_ULA_E_UPD_HLR_COMPL);
+ } else {
+ /* PERIODIC and REGULAR, as long we don't support multiple MSC with different Location Areas */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_LU_COMPL,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ }
+}
+
+/* 4.1.2.1 Node B */
+static void vlr_loc_upd_node_b(struct osmo_fsm_inst *fi)
+{
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* OsmoHLR does not support PgA, neither stores the IMEISV, so we have no need to update the HLR
+ * with either. TODO: depend on actual HLR configuration. See 3GPP TS 23.012 Release 14, process
+ * Update_Location_Area_VLR (ULA_VLR2). */
+ if (0) { /* IMEISV or PgA to send */
+ vlr_loc_upd_node_4(fi);
+ } else {
+ /* Location_Update_Completion */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_LU_COMPL,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ }
+}
+
+/* Non-standard: after Ciphering Mode Complete (or no ciph required) */
+static void vlr_loc_upd_post_ciph(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+ int rc;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ rc = lfp->vlr->ops.tx_common_id(lfp->msc_conn_ref);
+ if (rc)
+ LOGPFSML(fi, LOGL_ERROR, "Error while sending Common ID (%d)\n", rc);
+
+ vsub->conf_by_radio_contact_ind = true;
+ /* Update LAI */
+ vsub->cgi.lai = lfp->new_lai;
+ vsub->dormant_ind = false;
+ vsub->cancel_loc_rx = false;
+ if (hlr_update_needed(vsub)) {
+ vlr_loc_upd_node_4(fi);
+ } else {
+ /* TODO: ADD Support */
+ /* TODO: Node A: PgA Support */
+ vlr_loc_upd_node_b(fi);
+ }
+}
+
+/* 4.1.2.1 after Authentication successful (or no auth rqd) */
+static void vlr_loc_upd_post_auth(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+ bool umts_aka;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ /* Continue with ciphering, if enabled.
+ * If auth/ciph is optional and the HLR returned no auth info, continue without ciphering. */
+ if (!is_cmc_smc_to_be_attempted(lfp)
+ || (vsub->sec_ctx == VLR_SEC_CTX_NONE && !lfp->is_ciphering_required)) {
+ vlr_loc_upd_post_ciph(fi);
+ return;
+ }
+
+ if (!vsub->last_tuple) {
+ LOGPFSML(fi, LOGL_ERROR, "No auth tuple available\n");
+ lu_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ switch (vsub->sec_ctx) {
+ case VLR_SEC_CTX_GSM:
+ umts_aka = false;
+ break;
+ case VLR_SEC_CTX_UMTS:
+ umts_aka = true;
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "Cannot start ciphering, security context is not established\n");
+ lu_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ if (vlr_set_ciph_mode(vsub->vlr, fi, lfp->msc_conn_ref,
+ umts_aka,
+ vsub->vlr->cfg.retrieve_imeisv_ciphered)) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send Ciphering Mode Command\n");
+ lu_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_CIPH, LU_TIMEOUT_LONG, 0);
+}
+
+static void vlr_loc_upd_node1(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ if (try_auth(lfp)) {
+ /* Authenticate_VLR */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_AUTH,
+ LU_TIMEOUT_LONG, 0);
+ vsub->auth_fsm = auth_fsm_start(lfp->vsub,
+ fi,
+ VLR_ULA_E_AUTH_SUCCESS,
+ VLR_ULA_E_AUTH_NO_INFO,
+ VLR_ULA_E_AUTH_FAILURE,
+ lfp->is_r99,
+ lfp->is_utran);
+ } else {
+ /* no need for authentication */
+ vlr_loc_upd_post_auth(fi);
+ }
+}
+
+static void vlr_loc_upd_node1_pre(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (vlr->cfg.check_imei_rqd && vlr->cfg.retrieve_imeisv_early) {
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY, lu_fsm_state_tdef, vlr_tdefs, -1);
+ vlr_subscr_tx_req_check_imei(lfp->vsub);
+ } else {
+ vlr_loc_upd_node1(fi);
+ }
+}
+
+/* End of Check_IMEI Procedure. Executed early (before the location update), so we can send the IMEI to the HLR even if
+ * the MS would be rejected in LU. See the "Configuring the Subscribers Create on Demand Feature" section of the OsmoHLR
+ * user manual for a detailed explanation of the use case. */
+static void lu_fsm_wait_hlr_check_imei_early(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case VLR_ULA_E_HLR_IMEI_ACK:
+ vlr_loc_upd_node1(fi);
+ break;
+ case VLR_ULA_E_HLR_IMEI_NACK:
+ lu_fsm_failure(fi, GSM48_REJECT_ILLEGAL_ME);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+static void vlr_loc_upd_want_imsi(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(lfp->vsub);
+
+ /* Obtain_IMSI_VLR */
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_IMSI, lu_fsm_state_tdef, vlr_tdefs, -1);
+ vlr->ops.tx_id_req(lfp->msc_conn_ref, GSM_MI_TYPE_IMSI);
+ /* will continue at vlr_loc_upd_node1_pre() once IMSI arrives */
+}
+
+static int assoc_lfp_with_sub(struct osmo_fsm_inst *fi, struct vlr_subscr *vsub)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+
+ if (vsub->lu_fsm) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "A Location Updating process is already pending for"
+ " this subscriber. Aborting.\n");
+ /* Also get rid of the other pending LU attempt? */
+ /*lu_fsm_failure(vsub->lu_fsm, GSM48_REJECT_CONGESTION);*/
+ lu_fsm_failure(fi, GSM48_REJECT_CONGESTION);
+ return -EINVAL;
+ }
+ vsub->lu_fsm = fi;
+ vsub->msc_conn_ref = lfp->msc_conn_ref;
+ /* FIXME: send new LAC to HLR? */
+ vsub->cgi.lai.lac = lfp->new_lai.lac;
+ lfp->vsub = vsub;
+ /* Tell MSC to associate this subscriber with the given
+ * connection */
+ if (vlr->ops.subscr_assoc(lfp->msc_conn_ref, lfp->vsub))
+ lu_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return 0;
+}
+
+static int _lu_fsm_associate_vsub(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+ struct vlr_subscr *vsub = NULL;
+
+ if (!lfp->imsi[0]) {
+ /* TMSI was used */
+ lfp->lu_by_tmsi = true;
+ /* TMSI clash: if a different subscriber already has this TMSI,
+ * we will find that other subscriber in the VLR. So the IMSIs
+ * would mismatch, but we don't know about it. Theoretically,
+ * an authentication process would thwart any attempt to use
+ * someone else's TMSI.
+ * TODO: Otherwise we can ask for the IMSI and verify that it
+ * matches the IMSI on record. */
+ vsub = vlr_subscr_find_or_create_by_tmsi(vlr, lfp->tmsi, __func__, NULL);
+
+ if (!vsub) {
+ LOGPFSML(fi, LOGL_ERROR, "VLR subscriber allocation failed\n");
+ lu_fsm_failure(fi, GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
+ return -1;
+ }
+
+ vsub->sub_dataconf_by_hlr_ind = false;
+ if (assoc_lfp_with_sub(fi, vsub)) {
+ vlr_subscr_put(vsub, __func__);
+ return -1; /* error, fsm failure invoked in assoc_lfp_with_sub() */
+ }
+ vlr_subscr_put(vsub, __func__);
+ } else {
+ /* IMSI was used */
+ vsub = vlr_subscr_find_or_create_by_imsi(vlr, lfp->imsi, __func__, NULL);
+
+ if (!vsub) {
+ LOGPFSML(fi, LOGL_ERROR, "VLR subscriber allocation failed\n");
+ lu_fsm_failure(fi, GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
+ vlr_subscr_put(vsub, __func__);
+ return -1;
+ }
+
+ vsub->sub_dataconf_by_hlr_ind = false;
+ if (assoc_lfp_with_sub(fi, vsub)) {
+ vlr_subscr_put(vsub, __func__);
+ return -1; /* error, fsm failure invoked in assoc_lfp_with_sub() */
+ }
+ vlr_subscr_put(vsub, __func__);
+ }
+ return 0;
+}
+
+/* 4.1.2.1: Subscriber (via MSC/SGSN) requests location update */
+static void _start_lu_main(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+
+ /* TODO: PUESBINE related handling */
+
+ /* Is previous LAI in this VLR? */
+ if (!lai_in_this_vlr(vlr, &lfp->old_lai)) {
+#if 0
+ /* FIXME: check previous VLR, (3) */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_PVLR,
+ LU_TIMEOUT_LONG, 0);
+ return;
+#endif
+ LOGPFSML(fi, LOGL_NOTICE, "LAI change from %s,"
+ " but checking previous VLR not implemented\n",
+ osmo_lai_name(&lfp->old_lai));
+ }
+
+ /* If this is a TMSI based LU, we may not have the IMSI. Make sure that
+ * we know the IMSI, either on record, or request it. */
+ if (!lfp->vsub->imsi[0])
+ vlr_loc_upd_want_imsi(fi);
+ else
+ vlr_loc_upd_node1_pre(fi);
+}
+
+static void lu_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+
+ OSMO_ASSERT(event == VLR_ULA_E_UPDATE_LA);
+
+ if (_lu_fsm_associate_vsub(fi))
+ return; /* error. FSM already terminated. */
+
+ 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->lu_type == VLR_LU_TYPE_PERIODIC && lfp->vsub->imeisv[0])) {
+ /* R_IMEISV_IR1 passed */
+ _start_lu_main(fi);
+ } else {
+ vlr->ops.tx_id_req(lfp->msc_conn_ref, GSM_MI_TYPE_IMEISV);
+ osmo_tdef_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_IMEISV, lu_fsm_state_tdef, vlr_tdefs, -1);
+ }
+}
+
+static void lu_fsm_wait_imeisv(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case VLR_ULA_E_ID_IMEISV:
+ /* IMEISV was copied in vlr_subscr_rx_id_resp(), and that's
+ * where we received this event from. */
+ _start_lu_main(fi);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+/* Wait for response from Send_Identification to PVLR */
+static void lu_fsm_wait_pvlr(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case VLR_ULA_E_SEND_ID_ACK:
+ vlr_loc_upd_node1_pre(fi);
+ break;
+ case VLR_ULA_E_SEND_ID_NACK:
+ vlr_loc_upd_want_imsi(fi);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+/* Wait for result of Authenticate_VLR procedure */
+static void lu_fsm_wait_auth(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ enum gsm48_reject_value *res = data;
+
+ lfp->upd_hlr_vlr_fsm = NULL;
+
+ switch (event) {
+ case VLR_ULA_E_AUTH_SUCCESS:
+ /* Result == Pass */
+ vlr_loc_upd_post_auth(fi);
+ return;
+
+ case VLR_ULA_E_AUTH_FAILURE:
+ lu_fsm_failure(fi, res ? *res : GSM48_REJECT_NETWORK_FAILURE);
+ return;
+
+ case VLR_ULA_E_AUTH_NO_INFO:
+ /* HLR returned no auth info for the subscriber. Continue only if authentication is optional. */
+ if (lfp->authentication_required || lfp->is_ciphering_required) {
+ lu_fsm_failure(fi, res ? *res : GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+ LOGPFSML(fi, LOGL_INFO,
+ "Attaching subscriber without auth (auth is optional, and no auth info received from HLR)\n");
+ vlr_loc_upd_post_auth(fi);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lu_fsm_wait_ciph(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ enum vlr_ciph_result_cause result = VLR_CIPH_REJECT;
+
+ OSMO_ASSERT(event == VLR_ULA_E_CIPH_RES);
+
+ if (!data)
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: NULL\n");
+ else
+ result = *(enum vlr_ciph_result_cause*)data;
+
+ switch (result) {
+ case VLR_CIPH_COMPL:
+ vlr_loc_upd_post_ciph(fi);
+ return;
+ case VLR_CIPH_REJECT:
+ LOGPFSM(fi, "ciphering rejected\n");
+ lu_fsm_failure(fi, GSM48_REJECT_INVALID_MANDANTORY_INF);
+ return;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: %d\n", result);
+ lu_fsm_failure(fi, GSM48_REJECT_INVALID_MANDANTORY_INF);
+ return;
+ }
+}
+
+static void lu_fsm_wait_imsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+ char *mi_string = data;
+
+ switch (event) {
+ case VLR_ULA_E_ID_IMSI:
+ vlr_subscr_set_imsi(vsub, mi_string);
+ vlr_loc_upd_node1_pre(fi);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+/* At the end of Update_HLR_VLR */
+static void lu_fsm_wait_hlr_ul_res(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+
+ switch (event) {
+ case VLR_ULA_E_HLR_LU_RES:
+ /* pass-through this event to Update_HLR_VLR */
+ if (data == NULL)
+ osmo_fsm_inst_dispatch(lfp->upd_hlr_vlr_fsm, UPD_HLR_VLR_E_UPD_LOC_ACK, NULL);
+ else
+ osmo_fsm_inst_dispatch(lfp->upd_hlr_vlr_fsm, UPD_HLR_VLR_E_UPD_LOC_NACK, data);
+ break;
+ case VLR_ULA_E_UPD_HLR_COMPL:
+ if (data == NULL) {
+ /* successful case */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_LU_COMPL,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ /* continue in MSC ?!? */
+ } else {
+ /* unsuccessful case */
+ enum gsm48_reject_value cause =
+ *(enum gsm48_reject_value *)data;
+ /* Ignoring standalone mode for now. */
+ if (0 /* procedure_error && vlr->cfg.standalone_mode */) {
+ osmo_fsm_inst_state_chg(fi,
+ VLR_ULA_S_WAIT_LU_COMPL_STANDALONE,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ } else {
+ lu_fsm_failure(fi, cause);
+ }
+ }
+ break;
+ case VLR_ULA_E_ID_IMEI:
+ case VLR_ULA_E_ID_IMEISV:
+ /* Got the IMEI from ME, nothing to do right now though. */
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+/* Wait for end of Location_Update_Completion_VLR */
+static void lu_fsm_wait_lu_compl(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ uint8_t cause;
+
+ switch (event) {
+ case VLR_ULA_E_NEW_TMSI_ACK:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_NEW_TMSI_ACK, NULL);
+ break;
+ case VLR_ULA_E_ID_IMEI:
+ case VLR_ULA_E_ID_IMEISV:
+ /* Got the IMEI from ME, now send it to HLR */
+ vlr_subscr_tx_req_check_imei(lfp->vsub);
+ break;
+ case VLR_ULA_E_HLR_IMEI_ACK:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_IMEI_CHECK_ACK, NULL);
+ break;
+ case VLR_ULA_E_HLR_IMEI_NACK:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_IMEI_CHECK_NACK, NULL);
+ break;
+ case VLR_ULA_E_LU_COMPL_SUCCESS:
+ lu_fsm_discard_lu_compl_fsm(fi);
+
+ /* Update Register */
+ /* TODO: Set_Notification_Type 23.078 */
+ /* TODO: Notify_gsmSCF 23.078 */
+ /* TODO: Authenticated Radio Contact Established -> ARC */
+
+ if (lfp->lu_type == VLR_LU_TYPE_IMSI_ATTACH)
+ lfp->vlr->ops.tx_mm_info(lfp->msc_conn_ref);
+
+ lu_fsm_success(fi);
+ break;
+ case VLR_ULA_E_LU_COMPL_FAILURE:
+ cause = GSM48_REJECT_NETWORK_FAILURE;
+ if (data)
+ cause = *(uint8_t*)data;
+ lu_fsm_discard_lu_compl_fsm(fi);
+ lu_fsm_failure(fi, cause);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+/* Wait for end of Location_Update_Completion_VLR (standalone case) */
+static void lu_fsm_wait_lu_compl_standalone(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+ uint8_t cause;
+
+ switch (event) {
+ case VLR_ULA_E_NEW_TMSI_ACK:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_NEW_TMSI_ACK, NULL);
+ break;
+ case VLR_ULA_E_LU_COMPL_SUCCESS:
+ lu_fsm_discard_lu_compl_fsm(fi);
+ vsub->sub_dataconf_by_hlr_ind = false;
+ lu_fsm_success(fi);
+ break;
+ case VLR_ULA_E_LU_COMPL_FAILURE:
+ vsub->sub_dataconf_by_hlr_ind = false;
+ cause = GSM48_REJECT_NETWORK_FAILURE;
+ if (data)
+ cause = *(uint8_t*)data;
+ lu_fsm_discard_lu_compl_fsm(fi);
+ lu_fsm_failure(fi, cause);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+static void lu_fsm_reset_n(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ lfp->N = 0;
+}
+
+static const struct osmo_fsm_state vlr_lu_fsm_states[] = {
+ [VLR_ULA_S_IDLE] = {
+ .in_event_mask = S(VLR_ULA_E_UPDATE_LA),
+ .out_state_mask = S(VLR_ULA_S_WAIT_IMEISV) |
+ S(VLR_ULA_S_WAIT_PVLR) |
+ S(VLR_ULA_S_WAIT_IMSI) |
+ S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_IDLE),
+ .action = lu_fsm_idle,
+ },
+ [VLR_ULA_S_WAIT_IMEISV] = {
+ .in_event_mask = S(VLR_ULA_E_ID_IMEISV),
+ .out_state_mask = S(VLR_ULA_S_WAIT_PVLR) |
+ S(VLR_ULA_S_WAIT_IMSI) |
+ S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_IMEISV),
+ .action = lu_fsm_wait_imeisv,
+ },
+ [VLR_ULA_S_WAIT_PVLR] = {
+ .in_event_mask = S(VLR_ULA_E_SEND_ID_ACK) |
+ S(VLR_ULA_E_SEND_ID_NACK),
+ .out_state_mask = S(VLR_ULA_S_WAIT_IMSI) |
+ S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_PVLR),
+ .action = lu_fsm_wait_pvlr,
+ },
+ [VLR_ULA_S_WAIT_AUTH] = {
+ .in_event_mask = S(VLR_ULA_E_AUTH_SUCCESS) |
+ S(VLR_ULA_E_AUTH_NO_INFO) |
+ S(VLR_ULA_E_AUTH_FAILURE),
+ .out_state_mask = S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_AUTH),
+ .action = lu_fsm_wait_auth,
+ },
+ [VLR_ULA_S_WAIT_CIPH] = {
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_CIPH),
+ .in_event_mask = S(VLR_ULA_E_CIPH_RES),
+ .out_state_mask = S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .action = lu_fsm_wait_ciph,
+ },
+ [VLR_ULA_S_WAIT_IMSI] = {
+ .in_event_mask = S(VLR_ULA_E_ID_IMSI),
+ .out_state_mask = S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_IMSI),
+ .onenter = lu_fsm_reset_n,
+ .action = lu_fsm_wait_imsi,
+ },
+ [VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY] = {
+ .in_event_mask = S(VLR_ULA_E_HLR_IMEI_ACK) |
+ S(VLR_ULA_E_HLR_IMEI_NACK),
+ .out_state_mask = S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY),
+ .action = lu_fsm_wait_hlr_check_imei_early,
+ },
+ [VLR_ULA_S_WAIT_HLR_UPD] = {
+ .in_event_mask = S(VLR_ULA_E_HLR_LU_RES) |
+ S(VLR_ULA_E_UPD_HLR_COMPL) |
+ S(VLR_ULA_E_ID_IMEI) |
+ S(VLR_ULA_E_ID_IMEISV),
+ .out_state_mask = S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_LU_COMPL_STANDALONE) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_HLR_UPD),
+ .action = lu_fsm_wait_hlr_ul_res,
+ },
+ [VLR_ULA_S_WAIT_LU_COMPL] = {
+ .in_event_mask = S(VLR_ULA_E_LU_COMPL_SUCCESS) |
+ S(VLR_ULA_E_LU_COMPL_FAILURE) |
+ S(VLR_ULA_E_NEW_TMSI_ACK) |
+ S(VLR_ULA_E_ID_IMEI) |
+ S(VLR_ULA_E_ID_IMEISV) |
+ S(VLR_ULA_E_HLR_IMEI_ACK) |
+ S(VLR_ULA_E_HLR_IMEI_NACK),
+ .out_state_mask = S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_LU_COMPL),
+ .action = lu_fsm_wait_lu_compl,
+ },
+ [VLR_ULA_S_WAIT_LU_COMPL_STANDALONE] = {
+ .in_event_mask = S(VLR_ULA_E_LU_COMPL_SUCCESS) |
+ S(VLR_ULA_E_LU_COMPL_FAILURE) |
+ S(VLR_ULA_E_NEW_TMSI_ACK),
+ .out_state_mask = S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_LU_COMPL_STANDALONE),
+ .action = lu_fsm_wait_lu_compl_standalone,
+ },
+ [VLR_ULA_S_DONE] = {
+ .name = OSMO_STRINGIFY(VLR_ULA_S_DONE),
+ .onenter = lu_fsm_dispatch_result,
+ },
+};
+
+static void fsm_lu_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_subscr *vsub = lfp->vsub;
+
+ LOGPFSM(fi, "fsm_lu_cleanup called with cause %s\n",
+ osmo_fsm_term_cause_name(cause));
+ if (vsub && vsub->lu_fsm == fi)
+ vsub->lu_fsm = NULL;
+}
+
+static void fsm_lu_preterm(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ uint8_t gsm48_cause;
+
+ if (vlr_is_cs(lfp->vlr))
+ return;
+
+ if (cause != OSMO_FSM_TERM_TIMEOUT)
+ return;
+
+ switch (fi->state) {
+ case VLR_ULA_S_WAIT_IMSI:
+ gsm48_cause = GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE;
+ break;
+ case VLR_ULA_S_WAIT_AUTH:
+ default:
+ gsm48_cause = GSM48_REJECT_NETWORK_FAILURE;
+ break;
+ }
+
+ lfp->vlr->ops.tx_lu_rej(lfp->msc_conn_ref, gsm48_cause, lfp->lu_type);
+}
+
+int fsm_lu_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+ struct vlr_instance *vlr = lfp->vlr;
+
+ /* only PS requires resending, CS terminate FSM */
+ if (vlr_is_cs(vlr))
+ return 1;
+
+ /* PS: we have to resend the complete message 5x times, before failing */
+ if (++lfp->N >= 5)
+ return 1;
+
+ switch (fi->state) {
+ case VLR_ULA_S_WAIT_IMSI:
+ vlr->ops.tx_id_req(lfp->msc_conn_ref, GSM_MI_TYPE_IMSI);
+ break;
+ }
+
+ osmo_timer_schedule(&fi->timer, vlr_timer_secs(vlr, fi->T, fi->T), 0);
+
+ return 0;
+}
+
+
+static struct osmo_fsm vlr_lu_fsm = {
+ .name = "vlr_lu_fsm",
+ .states = vlr_lu_fsm_states,
+ .num_states = ARRAY_SIZE(vlr_lu_fsm_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DLGLOBAL,
+ .event_names = fsm_lu_event_names,
+ .cleanup = fsm_lu_cleanup,
+ .pre_term = fsm_lu_preterm,
+ .timer_cb = fsm_lu_timer_cb,
+};
+
+static inline struct lu_fsm_priv *lu_fsm_fi_priv(struct osmo_fsm_inst *fi)
+{
+ OSMO_ASSERT(fi->fsm == &vlr_lu_fsm);
+ return (struct lu_fsm_priv*)fi->priv;
+}
+
+static struct osmo_fsm_inst *
+_vlr_loc_update(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_lu_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *old_lai,
+ const struct osmo_location_area_id *new_lai,
+ bool authentication_required,
+ bool is_ciphering_to_be_attempted,
+ bool is_ciphering_required,
+ uint8_t key_seq,
+ bool is_r99, bool is_utran,
+ bool assign_tmsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu_fsm_priv *lfp;
+
+ if (is_ciphering_required)
+ OSMO_ASSERT(is_ciphering_to_be_attempted);
+
+ fi = osmo_fsm_inst_alloc_child(&vlr_lu_fsm, parent, parent_event_failure);
+ if (!fi)
+ return NULL;
+
+ lfp = talloc_zero(fi, struct lu_fsm_priv);
+ lfp->vlr = vlr;
+ lfp->msc_conn_ref = msc_conn_ref;
+ lfp->tmsi = tmsi;
+ lfp->lu_type = type;
+ lfp->old_lai = *old_lai;
+ lfp->new_lai = *new_lai;
+ lfp->lu_by_tmsi = true;
+ lfp->parent_event_success = parent_event_success;
+ lfp->parent_event_failure = parent_event_failure;
+ lfp->parent_event_data = parent_event_data;
+ lfp->authentication_required = authentication_required;
+ lfp->is_ciphering_to_be_attempted = is_ciphering_to_be_attempted;
+ lfp->is_ciphering_required = is_ciphering_required;
+ lfp->key_seq = key_seq;
+ lfp->is_r99 = is_r99;
+ lfp->is_utran = is_utran;
+ lfp->assign_tmsi = assign_tmsi;
+ if (imsi) {
+ strncpy(lfp->imsi, imsi, sizeof(lfp->imsi)-1);
+ lfp->imsi[sizeof(lfp->imsi)-1] = '\0';
+ lfp->lu_by_tmsi = false;
+ }
+ fi->priv = lfp;
+
+ return fi;
+}
+
+struct osmo_fsm_inst *
+vlr_loc_update(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_lu_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *old_lai,
+ const struct osmo_location_area_id *new_lai,
+ bool authentication_required,
+ bool is_ciphering_to_be_attempted,
+ bool is_ciphering_required,
+ uint8_t key_seq,
+ bool is_r99, bool is_utran,
+ bool assign_tmsi)
+{
+ struct osmo_fsm_inst *fi = _vlr_loc_update(
+ parent,
+ parent_event_success,
+ parent_event_failure,
+ parent_event_data,
+ vlr, msc_conn_ref,
+ type, tmsi, imsi,
+ old_lai,
+ new_lai,
+ authentication_required,
+ is_ciphering_to_be_attempted,
+ is_ciphering_required,
+ key_seq,
+ is_r99, is_utran,
+ assign_tmsi);
+
+ if (!fi)
+ return NULL;
+
+ LOGPFSM(fi, "rev=%s net=%s%s%s\n",
+ is_r99 ? "R99" : "GSM",
+ is_utran ? "UTRAN" : "GERAN",
+ (authentication_required || is_ciphering_to_be_attempted) ?
+ " Auth" : " (no Auth)",
+ (authentication_required || is_ciphering_to_be_attempted) ?
+ (is_ciphering_to_be_attempted ? "+Ciph" : " (no Ciph)")
+ : "");
+
+ if (is_utran && !authentication_required)
+ LOGPFSML(fi, LOGL_ERROR,
+ "Authentication off on UTRAN network. Good luck.\n");
+
+ osmo_fsm_inst_dispatch(fi, VLR_ULA_E_UPDATE_LA, NULL);
+
+ return fi;
+}
+
+
+struct osmo_fsm_inst *
+vlr_ra_update(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_lu_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_routing_area_id *old_rai,
+ const struct osmo_routing_area_id *new_rai,
+ bool authentication_required,
+ bool is_ciphering_to_be_attempted,
+ bool is_ciphering_required,
+ uint8_t key_seq,
+ bool is_r99, bool is_utran,
+ bool assign_tmsi)
+{
+ struct lu_fsm_priv *lfp;
+ struct osmo_fsm_inst *fi = _vlr_loc_update(
+ parent,
+ parent_event_success,
+ parent_event_failure,
+ parent_event_data,
+ vlr, msc_conn_ref,
+ type, tmsi, imsi,
+ &old_rai->lac,
+ &new_rai->lac,
+ authentication_required,
+ is_ciphering_to_be_attempted,
+ is_ciphering_required,
+ key_seq,
+ is_r99, is_utran,
+ assign_tmsi);
+
+ if (!fi)
+ return NULL;
+
+ lfp = fi->priv;
+ lfp->old_rai = *old_rai;
+ lfp->new_rai = *new_rai;
+
+ LOGPFSM(fi, "rev=%s net=%s%s%s\n",
+ is_r99 ? "R99" : "GSM",
+ is_utran ? "UTRAN" : "GERAN",
+ (authentication_required || is_ciphering_to_be_attempted) ?
+ " Auth" : " (no Auth)",
+ (authentication_required || is_ciphering_to_be_attempted) ?
+ (is_ciphering_to_be_attempted ? "+Ciph" : " (no Ciph)")
+ : "");
+
+ if (is_utran && !authentication_required)
+ LOGPFSML(fi, LOGL_ERROR,
+ "Authentication off on UTRAN network. Good luck.\n");
+
+ osmo_fsm_inst_dispatch(fi, VLR_ULA_E_UPDATE_LA, NULL);
+
+ return fi;
+}
+
+void vlr_loc_update_cancel(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause fsm_cause,
+ uint8_t gsm48_cause)
+{
+ struct lu_fsm_priv *lfp;
+
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(fi->fsm == &vlr_lu_fsm);
+
+ lfp = fi->priv;
+ lfp->rej_cause = gsm48_cause;
+
+ if (fi->state != VLR_ULA_S_DONE)
+ lu_fsm_failure(fi, gsm48_cause);
+}
+
+void vlr_lu_fsm_init(bool is_ps)
+{
+ if (is_ps) {
+ lu_fsm_state_tdef = sgsn_lu_tdef_states;
+ lu_compl_fsm_state_tdef = sgsn_lu_compl_tdef_states;
+ } else {
+ lu_fsm_state_tdef = msc_lu_tdef_states;
+ lu_compl_fsm_state_tdef = msc_lu_compl_tdef_states;
+ }
+
+ OSMO_ASSERT(osmo_fsm_register(&vlr_lu_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&upd_hlr_vlr_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&sub_pres_vlr_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&lu_compl_vlr_fsm) == 0);
+}
+
+void vlr_lu_fsm_set_log_subsys(int log_subsys)
+{
+ vlr_lu_fsm.log_subsys = log_subsys;
+ upd_hlr_vlr_fsm.log_subsys = log_subsys;
+ sub_pres_vlr_fsm.log_subsys = log_subsys;
+ lu_compl_vlr_fsm.log_subsys = log_subsys;
+}
diff --git a/src/libvlr/vlr_lu_fsm.h b/src/libvlr/vlr_lu_fsm.h
new file mode 100644
index 0000000..1eee75d
--- /dev/null
+++ b/src/libvlr/vlr_lu_fsm.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+
+enum vlr_lu_state {
+ VLR_ULA_S_IDLE,
+ VLR_ULA_S_WAIT_IMEISV,
+ VLR_ULA_S_WAIT_PVLR, /* Waiting for ID from PVLR */
+ VLR_ULA_S_WAIT_AUTH, /* Waiting for Authentication */
+ VLR_ULA_S_WAIT_CIPH, /* Waiting for Ciphering Complete */
+ VLR_ULA_S_WAIT_IMSI, /* Waiting for IMSI from MS */
+ VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY, /* Waiting for Check IMEI result from HLR */
+ VLR_ULA_S_WAIT_HLR_UPD, /* Waiting for end of HLR update */
+ VLR_ULA_S_WAIT_LU_COMPL,/* Waiting for LU complete */
+ VLR_ULA_S_WAIT_LU_COMPL_STANDALONE, /* Standalone VLR */
+ VLR_ULA_S_DONE
+};
+
+void vlr_lu_fsm_init(bool is_ps);
+void vlr_lu_fsm_set_log_subsys(int log_subsys);
diff --git a/src/libvlr/vlr_sgs.c b/src/libvlr/vlr_sgs.c
new file mode 100644
index 0000000..f7fea09
--- /dev/null
+++ b/src/libvlr/vlr_sgs.c
@@ -0,0 +1,369 @@
+/* (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 <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/vlr/vlr.h>
+#include <osmocom/vlr/vlr_sgs.h>
+
+#include "vlr_core.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 instance. */
+void vlr_sgs_reset(struct vlr_instance *vlr)
+{
+ struct vlr_subscr *vsub;
+
+ OSMO_ASSERT(vlr);
+
+ LOGSGS(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 instance.
+ * \param[in] cfg SGs interface configuration parameters.
+ * \param[in] response_cb callback function that is called when LU is done.
+ * \param[in] paging_cb callback function that is called when LU needs to page.
+ * \param[in] mminfo_cb callback 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.
+ * \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_plmn_id *last_eutran_plmn)
+{
+ 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, VSUB_USE_SGS_LU, NULL);
+ if (!vsub) {
+ LOGSGS(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);
+ 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);
+
+ /* 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;
+
+ /* Subscribers that are created by the SGs location update will not
+ * expire automatically, however a 2G LU or an implicit IMSI detach
+ * from EPS services may change this. */
+ vsub->expire_lu = VLR_SUBSCRIBER_NO_EXPIRATION;
+
+ 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);
+
+ /* Balance vlr_subscr_find_or_create_by_imsi() in vlr_sgs_loc_update() */
+ vlr_subscr_put(vsub, VSUB_USE_SGS_LU);
+
+ /* 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);
+ /* Balance vlr_subscr_find_or_create_by_imsi() in vlr_sgs_loc_update() */
+ vlr_subscr_put(vsub, VSUB_USE_SGS_LU);
+}
+
+/*! 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, __func__);
+ if (!vsub)
+ return;
+
+ /* See also: 3GPP TS 29.118, 5.6.3 Procedures in the VLR: In case of
+ * an implicit detach, we are supposed to check if the state of the
+ * SGs-association, and only when it is not SGs-NULL, we may proceed. */
+ if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && type == SGSAP_ID_NONEPS_T_IMPLICIT_UE_EPS_NONEPS) {
+ vlr_subscr_put(vsub, __func__);
+ 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:
+ LOGSGS(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);
+
+ /* Detaching from non EPS services essentially means that the
+ * subscriber is detached from 2G. In any case the VLR will
+ * get rid of the subscriber. */
+ vlr_subscr_expire(vsub);
+ vlr_subscr_put(vsub, __func__);
+}
+
+/*! 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, __func__);
+ 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:
+ LOGSGS(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);
+
+ /* See also 3GPP TS 29.118, 5.4.3 Procedures in the VLR. Detaching from
+ * EPS services essentially means that the subscriber leaves the 4G RAN
+ * but continues to live on 2G, this basically turns the subscriber into
+ * a normal 2G subscriber and we need to make sure that the lu-
+ * expiration timer is running. */
+ if (vsub->expire_lu == VLR_SUBSCRIBER_NO_EXPIRATION)
+ vlr_subscr_enable_expire_lu(vsub);
+
+ vlr_subscr_put(vsub, __func__);
+}
+
+/*! 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, __func__);
+ if (!vsub)
+ return;
+
+ osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_TMSI_REALLOC, NULL);
+ vlr_subscr_put(vsub, __func__);
+}
+
+/*! 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, __func__);
+ 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);
+ LOGSGS(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);
+ /* Balance ref count increment from vlr_sgs_pag() */
+ vlr_subscr_put(vsub, VSUB_USE_SGS_PAGING_REQ);
+
+ vlr_subscr_put(vsub, __func__);
+}
+
+/*! 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, __func__);
+ if (!vsub)
+ return;
+
+ /* Stop Ts5 and and consider the paging as successful */
+ osmo_timer_del(&vsub->sgs.Ts5);
+ /* Balance ref count increment from vlr_sgs_pag() */
+ vlr_subscr_put(vsub, VSUB_USE_SGS_PAGING_REQ);
+
+ vlr_subscr_put(vsub, __func__);
+}
+
+/*! 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, __func__);
+ 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);
+ LOGSGS(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, __func__);
+}
+
+/* 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 specific 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. */
+
+ LOGSGS(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, VSUB_USE_SGS_PAGING_REQ);
+
+ 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).
+ * Apparently 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, VSUB_USE_SGS_PAGING_REQ);
+}
+
+/*! 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 0000000..f576b86
--- /dev/null
+++ b/src/libvlr/vlr_sgs_fsm.c
@@ -0,0 +1,416 @@
+/* (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/vlr/vlr.h>
+#include <osmocom/vlr/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}
+};
+
+/* 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 relevant 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 we remove recorded Last EUTRAN PLMN Id when UE ceases to be
+ * available over SGs */
+ vlr_subscr_set_last_used_eutran_plmn_id(vsub, NULL);
+
+ /* Make sure any ongoing paging is aborted. */
+ if (vsub->cs.is_paging && vsub->sgs.paging_cb)
+ vsub->sgs.paging_cb(vsub, SGSAP_SERV_IND_PAGING_TIMEOUT);
+
+ /* Ensure that Ts5 (pending paging via SGs) is deleted */
+ if (vlr_sgs_pag_pend(vsub))
+ osmo_timer_del(&vsub->sgs.Ts5);
+}
+
+/* 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;
+ struct sgs_lu_response sgs_lu_response = {0};
+ int rc;
+
+ /* 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));
+ goto error;
+ }
+
+ rc = vlr_subscr_req_lu(vsub);
+ if (rc != 0) {
+ LOGPFSML(fi, LOGL_ERROR, "(sub %s) HLR LU request failed\n", vlr_subscr_name(vsub));
+ goto error;
+ }
+
+ osmo_fsm_inst_state_chg(fi, SGS_UE_ST_LA_UPD_PRES, 0, 0);
+ vsub->ms_not_reachable_flag = false;
+ return;
+
+error:
+ to_null(fi);
+ sgs_lu_response.error = true;
+ sgs_lu_response.vsub = vsub;
+ vsub->sgs.response_cb(&sgs_lu_response);
+}
+
+/* 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;
+
+ if (!vsub->lu_complete) {
+ vsub->lu_complete = true;
+ /* Balanced by vlr_subscr_expire() */
+ vlr_subscr_get(vsub, VSUB_USE_ATTACHED);
+ }
+
+ 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 acknowledge 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,
+ * so 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);
+ break;
+ 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 = DLGLOBAL,
+ .event_names = sgs_ue_fsm_event_names,
+};
+
+/*! Initialize/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_ASSERT(osmo_fsm_register(&sgs_ue_fsm) == 0);
+}
+
+/*! Set the log level of the fsm */
+void vlr_sgs_fsm_set_log_subsys(int log_level)
+{
+ sgs_ue_fsm.log_subsys = log_level;
+}
+
+/*! 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 0000000..f36ed32
--- /dev/null
+++ b/src/libvlr/vlr_sgs_fsm.h
@@ -0,0 +1,45 @@
+/* (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
+
+struct vlr_subscr;
+
+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_set_log_subsys(int log_subsys);
+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);
diff --git a/src/sgsn/Makefile.am b/src/sgsn/Makefile.am
index 660e490..a2c6901 100644
--- a/src/sgsn/Makefile.am
+++ b/src/sgsn/Makefile.am
@@ -80,6 +80,7 @@
$(top_builddir)/src/gprs/crc24.o \
$(top_builddir)/src/gprs/gprs_utils.o \
$(top_builddir)/src/gprs/sgsn_ares.o \
+ $(top_builddir)/src/libvlr/libvlr.a \
$(OSMO_LIBS) \
$(LIBOSMOGSUPCLIENT_LIBS) \
$(LIBCARES_LIBS) \
diff --git a/tests/gprs_routing_area/Makefile.am b/tests/gprs_routing_area/Makefile.am
index f95d3ae..e15769d 100644
--- a/tests/gprs_routing_area/Makefile.am
+++ b/tests/gprs_routing_area/Makefile.am
@@ -72,6 +72,7 @@
$(top_builddir)/src/gprs/gprs_llc_parse.o \
$(top_builddir)/src/gprs/crc24.o \
$(top_builddir)/src/gprs/sgsn_ares.o \
+ $(top_builddir)/src/libvlr/libvlr.a \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOGSM_LIBS) \
diff --git a/tests/sgsn/Makefile.am b/tests/sgsn/Makefile.am
index f698774..85b6d76 100644
--- a/tests/sgsn/Makefile.am
+++ b/tests/sgsn/Makefile.am
@@ -86,6 +86,7 @@
$(top_builddir)/src/gprs/gprs_llc_parse.o \
$(top_builddir)/src/gprs/crc24.o \
$(top_builddir)/src/gprs/sgsn_ares.o \
+ $(top_builddir)/src/libvlr/libvlr.a \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOGSM_LIBS) \

To view, visit change 39560. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-sgsn
Gerrit-Branch: master
Gerrit-Change-Id: I221114e00e6f338a75ae0dd7ac285e262b23635d
Gerrit-Change-Number: 39560
Gerrit-PatchSet: 1
Gerrit-Owner: lynxis lazus <lynxis@fe80.eu>