lynxis lazus has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-sgsn/+/39560?usp=email )
Change subject: add libvlr based on MSC commit 25362cc295036a4671804db9003c3d22a88309d2 ......................................................................
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) \