aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2016-06-17 00:06:42 +0200
committerHarald Welte <laforge@gnumonks.org>2016-06-19 19:32:27 +0200
commit7b1bbbea60b9b04ff20e67561c201fd08ed42ab6 (patch)
treea1828390407df0d09c7c74212d325f5f00cccda3
parentadc168dcef4b7819f9cac392a92c7cd041baf664 (diff)
WIP: Introduce libvlr
-rw-r--r--openbsc/configure.ac2
-rw-r--r--openbsc/include/openbsc/Makefile.am2
-rw-r--r--openbsc/include/openbsc/debug.h1
-rw-r--r--openbsc/include/openbsc/gsm_data.h15
-rw-r--r--openbsc/include/openbsc/vlr.h240
-rw-r--r--openbsc/src/Makefile.am2
-rw-r--r--openbsc/src/gprs/gprs_gsup_client.c4
-rw-r--r--openbsc/src/libcommon/debug.c5
-rw-r--r--openbsc/src/libmsc/gsm_04_08.c432
-rw-r--r--openbsc/src/libmsc/gsm_04_11.c1
-rw-r--r--openbsc/src/libmsc/osmo_msc.c15
-rw-r--r--openbsc/src/libmsc/transaction.c32
-rw-r--r--openbsc/src/libvlr/Makefile.am9
-rw-r--r--openbsc/src/libvlr/vlr.c795
-rw-r--r--openbsc/src/libvlr/vlr_access_req_fsm.c571
-rw-r--r--openbsc/src/libvlr/vlr_access_req_fsm.h36
-rw-r--r--openbsc/src/libvlr/vlr_auth_fsm.c495
-rw-r--r--openbsc/src/libvlr/vlr_auth_fsm.h40
-rw-r--r--openbsc/src/libvlr/vlr_core.h25
-rw-r--r--openbsc/src/libvlr/vlr_lu_fsm.c1088
-rw-r--r--openbsc/src/libvlr/vlr_lu_fsm.h5
-rw-r--r--openbsc/src/osmo-nitb/Makefile.am1
-rw-r--r--openbsc/tests/Makefile.am2
-rw-r--r--openbsc/tests/vlr/Makefile.am16
-rw-r--r--openbsc/tests/vlr/vlr_test.c684
25 files changed, 4197 insertions, 321 deletions
diff --git a/openbsc/configure.ac b/openbsc/configure.ac
index 5cd5d2d80..cfae2d348 100644
--- a/openbsc/configure.ac
+++ b/openbsc/configure.ac
@@ -200,6 +200,7 @@ AC_OUTPUT(
src/libtrau/Makefile
src/libbsc/Makefile
src/libmsc/Makefile
+ src/libvlr/Makefile
src/libmgcp/Makefile
src/libcommon/Makefile
src/libfilter/Makefile
@@ -229,6 +230,7 @@ AC_OUTPUT(
tests/oap/Makefile
tests/gtphub/Makefile
tests/mm_auth/Makefile
+ tests/vlr/Makefile
doc/Makefile
doc/examples/Makefile
Makefile)
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
index ea5c93b5f..7b2a6ee63 100644
--- a/openbsc/include/openbsc/Makefile.am
+++ b/openbsc/include/openbsc/Makefile.am
@@ -18,7 +18,7 @@ noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \
gprs_gb_parse.h smpp.h meas_feed.h \
gprs_gsup_client.h bsc_msg_filter.h \
oap.h \
- gtphub.h
+ gtphub.h vlr.h
openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
openbscdir = $(includedir)/openbsc
diff --git a/openbsc/include/openbsc/debug.h b/openbsc/include/openbsc/debug.h
index 43ebb19a0..7972bc823 100644
--- a/openbsc/include/openbsc/debug.h
+++ b/openbsc/include/openbsc/debug.h
@@ -36,6 +36,7 @@ enum {
DGTPHUB,
DRANAP,
DSUA,
+ DVLR,
Debug_LastEntry,
};
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 0ae818a4d..c64038e05 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -61,20 +61,6 @@ struct gsm_auth_tuple {
#define GSM_KEY_SEQ_INVAL 7 /* GSM 04.08 - 10.5.1.2 */
/*
- * LOCATION UPDATING REQUEST state
- *
- * Our current operation is:
- * - Get imei/tmsi
- * - Accept/Reject according to global policy
- */
-struct gsm_loc_updating_operation {
- struct osmo_timer_list updating_timer;
- unsigned int waiting_for_imsi : 1;
- unsigned int waiting_for_imei : 1;
- unsigned int key_seq : 4;
-};
-
-/*
* AUTHENTICATION/CIPHERING state
*/
struct gsm_security_operation {
@@ -125,7 +111,6 @@ struct gsm_subscriber_connection {
/*
* Operations that have a state and might be pending
*/
- struct gsm_loc_updating_operation *loc_operation;
struct gsm_security_operation *sec_operation;
struct gsm_anchor_operation *anch_operation;
diff --git a/openbsc/include/openbsc/vlr.h b/openbsc/include/openbsc/vlr.h
new file mode 100644
index 000000000..6112cc82f
--- /dev/null
+++ b/openbsc/include/openbsc/vlr.h
@@ -0,0 +1,240 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <openbsc/gsm_data.h>
+// for GSM_NAME_LENGTH
+#include <openbsc/gsm_subscriber.h>
+
+/* 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_sub_auth_state {
+ /* subscriber needs to be autenticated */
+ 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_RES, /* Result of auth procedure */
+ VLR_ULA_E_ID_IMSI, /* IMSI recieved from MS */
+ VLR_ULA_E_ID_IMEI, /* IMEI received from MS */
+ VLR_ULA_E_ID_IMEISV, /* IMEISV received from MS */
+ VLR_ULA_E_HLR_LU_RES, /* HLR UpdateLocation result */
+ VLR_ULA_E_UPD_HLR_COMPL,/* UpdatE_HLR_VLR result */
+ VLR_ULA_E_LU_COMPL_TERM,/* Location_Update_Completion_VLR result */
+ VLR_ULA_E_NEW_TMSI_ACK, /* TMSI Reallocation Complete */
+};
+
+enum vlr_sub_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;
+
+/* 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_subscriber {
+ struct llist_head list;
+ struct vlr_instance *vlr;
+
+ /* Data from HLR */
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1]; /* 2.1.1.1 */
+ char msisdn[15+1]; /* 2.1.2 */
+ OSMO_LBUF_DECL(hlr, 16); /* 2.4.7 */
+ uint32_t periodic_lu_timer; /* 2.4.24 */
+ uint32_t age_indicator; /* 2.17.1 */
+ char name[GSM_NAME_LENGTH]; /* proprietary */
+
+ /* Authentication Data */
+ struct gsm_auth_tuple auth_tuples[5]; /* 2.3.1-2.3.4 */
+ struct gsm_auth_tuple *last_tuple;
+ enum vlr_sub_security_context sec_ctx;
+
+ /* Data local to VLR is below */
+ uint32_t tmsi; /* 2.1.4 */
+
+ /* some redundancy in information below? */
+ struct osmo_cell_global_id cgi; /* 2.4.16 */
+ uint16_t lac; /* 2.4.2 */
+
+ char imeisv[GSM23003_IMEISV_NUM_DIGITS+1]; /* 2.2.3 */
+ char imei[GSM23003_IMEISV_NUM_DIGITS+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;
+
+ uint32_t flags;
+ int auth_error_cause;
+ int auth_tuples_updated;
+ int authorized;
+ int use_count;
+ time_t expire_lu; /* FIXME: overlap with periodic_lu_timer/age_indicator */
+
+ struct osmo_fsm_inst *auth_fsm;
+ struct osmo_fsm_inst *lu_fsm;
+ struct omso_fsm_inst *proc_arq_fsm;
+
+ void *msc_conn_ref;
+
+ /* PS (SGSN) specific parts */
+ struct {
+ struct llist_head pdp_list;
+ uint8_t rac;
+ uint8_t sac;
+ struct gprs_mm_ctx *mmctx;
+ } ps;
+ /* VLR specific parts */
+ struct {
+ /* pending requests */
+ int is_paging;
+ struct llist_head requests;
+ } cs;
+};
+
+struct vlr_ops {
+ /* encode + transmit an AUTH REQ towards the MS */
+ int (*tx_auth_req)(void *msc_conn_ref,
+ struct gsm_auth_tuple *at);
+ /* 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_ack)(void *msc_conn_ref);
+ int (*tx_lu_rej)(void *msc_conn_ref, uint8_t cause);
+
+ int (*set_ciph_mode)(void *msc_conn_ref);
+
+ /* notify MSC/SGSN that the subscriber data in VLR has been updated */
+ void (*subscr_update)(struct vlr_subscriber *vsub);
+ /* notify MSC/SGSN that the given subscriber has bene associated
+ * with this msc_conn_ref */
+ void (*subscr_assoc)(void *msc_conn_ref, struct vlr_subscriber *vsub);
+};
+
+enum vlr_timer {
+ VLR_T_3250,
+ VLR_T_3260,
+ VLR_T_3270,
+ _NUM_VLR_TIMERS
+};
+
+/* An instance of the VLR codebase */
+struct vlr_instance {
+ struct llist_head subscribers;
+ struct llist_head operations;
+ struct gprs_gsup_client *gsup_client;
+ struct vlr_ops ops;
+ struct {
+ bool retrieve_imeisv;
+ bool alloc_tmsi;
+ bool check_imei_rqd;
+ bool auth_reuse_old_sets;
+ bool parq_retrieve_imsi;
+ bool is_ps;
+ uint32_t timer[_NUM_VLR_TIMERS];
+ } cfg;
+};
+
+struct vlr_subscriber *
+vlr_loc_update(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);
+
+/* Process_Access_Request (CM SERV REQ / PAGING RESP) */
+struct vlr_subscriber *
+vlr_process_access_req(struct vlr_instance *vlr, void *msc_conn_ref, uint32_t tmsi,
+ const char *imsi, const struct osmo_location_area_id *lai);
+
+/* tell the VLR that the subscriber connection is gone */
+int vlr_sub_disconnected(struct vlr_subscriber *vsub);
+
+int vlr_sub_rx_id_resp(struct vlr_subscriber *vsub, const uint8_t *mi, size_t mi_len);
+int vlr_sub_rx_auth_resp(struct vlr_subscriber *vsub, bool is_r99, bool is_utran,
+ const uint8_t *res, uint8_t res_len);
+int vlr_sub_rx_auth_fail(struct vlr_subscriber *vsub, const uint8_t *auts);
+int vlr_sub_tx_auth_fail_rep(struct vlr_subscriber *vsub);
+
+struct vlr_instance *
+vlr_init(void *ctx, const struct vlr_ops *ops, const char *addr_str, uint16_t port);
+
+
+/* internal use only */
+
+struct osmo_fsm_inst *sub_pres_vlr_fsm_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscriber *vsub,
+ uint32_t term_event);
+struct osmo_fsm_inst *
+upd_hlr_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscriber *vsub,
+ uint32_t parent_event);
+
+struct osmo_fsm_inst *
+lu_compl_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscriber *vsub,
+ void *msc_conn_ref,
+ uint32_t term_event);
+
+
+const char *vlr_sub_name(struct vlr_subscriber *vsub);
+struct vlr_subscriber *
+vlr_subscr_find_by_imsi(struct vlr_instance *vlr, const char *imsi);
+struct vlr_subscriber *
+vlr_subscr_find_by_tmsi(struct vlr_instance *vlr, uint32_t tmsi);
+struct vlr_subscriber *vlr_sub_alloc(struct vlr_instance *vlr);
+void vlr_sub_cleanup(struct vlr_subscriber *vsub);
+void vlr_sub_cancel(struct vlr_subscriber *vsub);
+int vlr_sub_alloc_tmsi(struct vlr_subscriber *vsub);
+
+uint32_t vlr_timer(struct vlr_instance *vlr, uint32_t timer);
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
index 6f6174eb1..241dd8e4a 100644
--- a/openbsc/src/Makefile.am
+++ b/openbsc/src/Makefile.am
@@ -2,7 +2,7 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS)
-SUBDIRS = libcommon libmgcp libbsc libmsc libtrau libfilter osmo-nitb osmo-bsc_mgcp utils ipaccess gprs
+SUBDIRS = libcommon libvlr libmgcp libbsc libmsc libtrau libfilter osmo-nitb osmo-bsc_mgcp utils ipaccess gprs
# Conditional modules
if BUILD_NAT
diff --git a/openbsc/src/gprs/gprs_gsup_client.c b/openbsc/src/gprs/gprs_gsup_client.c
index c07aa8daa..2a3c14504 100644
--- a/openbsc/src/gprs/gprs_gsup_client.c
+++ b/openbsc/src/gprs/gprs_gsup_client.c
@@ -320,11 +320,15 @@ void gprs_gsup_client_destroy(struct gprs_gsup_client *gsupc)
int gprs_gsup_client_send(struct gprs_gsup_client *gsupc, struct msgb *msg)
{
if (!gsupc) {
+ LOGP(DGPRS, LOGL_NOTICE, "No GSUP client, unable to "
+ "send %s\n", msgb_hexdump(msg));
msgb_free(msg);
return -ENOTCONN;
}
if (!gsupc->is_connected) {
+ LOGP(DGPRS, LOGL_NOTICE, "GSUP not connected, unable to "
+ "send %s\n", msgb_hexdump(msg));
msgb_free(msg);
return -EAGAIN;
}
diff --git a/openbsc/src/libcommon/debug.c b/openbsc/src/libcommon/debug.c
index cf5beeb44..ecab3d7b4 100644
--- a/openbsc/src/libcommon/debug.c
+++ b/openbsc/src/libcommon/debug.c
@@ -175,6 +175,11 @@ static const struct log_info_cat default_categories[] = {
.description = "SCCP User Adaptation Protocol",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
+ [DVLR] = {
+ .name = "DVLR",
+ .description = "Visitor Location Register",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
};
enum log_filter {
diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c
index 4f8864a42..f00da821b 100644
--- a/openbsc/src/libmsc/gsm_04_08.c
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -1,7 +1,7 @@
/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
* 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
-/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2008-2016 by Harald Welte <laforge@gnumonks.org>
* (C) 2008-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
*
* All Rights Reserved
@@ -58,6 +58,7 @@
#include <openbsc/mncc_int.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/core/bitvec.h>
+#include <openbsc/vlr.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm0480.h>
@@ -72,12 +73,13 @@
void *tall_locop_ctx;
void *tall_authciphop_ctx;
+static struct vlr_instance *g_vlr;
+
static int tch_rtp_signal(struct gsm_lchan *lchan, int signal);
static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn);
static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
uint8_t pdisc, uint8_t msg_type);
-static void schedule_reject(struct gsm_subscriber_connection *conn);
static void release_anchor(struct gsm_subscriber_connection *conn);
struct gsm_lai {
@@ -249,154 +251,7 @@ int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
return -EINVAL; /* not reached */
}
-static bool subscr_regexp_check(const struct gsm_network *net, const char *imsi)
-{
- if (!net->authorized_reg_str)
- return false;
-
- if (regexec(&net->authorized_regexp, imsi, 0, NULL, 0) != REG_NOMATCH)
- return true;
-
- return false;
-}
-
-static int authorize_subscriber(struct gsm_loc_updating_operation *loc,
- struct gsm_subscriber *subscriber)
-{
- if (!subscriber)
- return 0;
-
- /*
- * Do not send accept yet as more information should arrive. Some
- * phones will not send us the information and we will have to check
- * what we want to do with that.
- */
- if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei))
- return 0;
-
- switch (subscriber->group->net->auth_policy) {
- case GSM_AUTH_POLICY_CLOSED:
- return subscriber->authorized;
- case GSM_AUTH_POLICY_REGEXP:
- if (subscriber->authorized)
- return 1;
- if (subscr_regexp_check(subscriber->group->net,
- subscriber->imsi))
- subscriber->authorized = 1;
- return subscriber->authorized;
- case GSM_AUTH_POLICY_TOKEN:
- if (subscriber->authorized)
- return subscriber->authorized;
- return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT);
- case GSM_AUTH_POLICY_ACCEPT_ALL:
- return 1;
- default:
- return 0;
- }
-}
-
-static void release_loc_updating_req(struct gsm_subscriber_connection *conn, int release)
-{
- if (!conn->loc_operation)
- return;
-
- /* No need to keep the connection up */
- release_anchor(conn);
-
- osmo_timer_del(&conn->loc_operation->updating_timer);
- talloc_free(conn->loc_operation);
- conn->loc_operation = NULL;
- subscr_con_put(conn);
-}
-
-static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
-{
- if (conn->loc_operation)
- LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n");
- release_loc_updating_req(conn, 0);
-
- conn->loc_operation = talloc_zero(tall_locop_ctx,
- struct gsm_loc_updating_operation);
- if (conn->loc_operation)
- subscr_con_get(conn);
-}
-
-static int finish_lu(struct gsm_subscriber_connection *conn)
-{
- int rc = 0;
- int avoid_tmsi = conn->bts->network->avoid_tmsi;
-
- /* We're all good */
- if (avoid_tmsi) {
- conn->subscr->tmsi = GSM_RESERVED_TMSI;
- db_sync_subscriber(conn->subscr);
- } else {
- db_subscriber_alloc_tmsi(conn->subscr);
- }
-
- rc = gsm0408_loc_upd_acc(conn);
- if (conn->bts->network->send_mm_info) {
- /* send MM INFO with network name */
- rc = gsm48_tx_mm_info(conn);
- }
-
- /* call subscr_update after putting the loc_upd_acc
- * in the transmit queue, since S_SUBSCR_ATTACHED might
- * trigger further action like SMS delivery */
- subscr_update(conn->subscr, conn->bts,
- GSM_SUBSCRIBER_UPDATE_ATTACHED);
-
- /*
- * The gsm0408_loc_upd_acc sends a MI with the TMSI. The
- * MS needs to respond with a TMSI REALLOCATION COMPLETE
- * (even if the TMSI is the same).
- */
- if (avoid_tmsi)
- release_loc_updating_req(conn, 1);
-
- return rc;
-}
-
-static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event,
- struct msgb *msg, void *data, void *param)
-{
- struct gsm_subscriber_connection *conn = data;
- int rc = 0;
-
- switch (event) {
- case GSM_SECURITY_AUTH_FAILED:
- release_loc_updating_req(conn, 1);
- break;
-
- case GSM_SECURITY_ALREADY:
- LOGP(DMM, LOGL_ERROR, "We don't expect LOCATION "
- "UPDATING after CM SERVICE REQUEST\n");
- /* fall through */
-
- case GSM_SECURITY_NOAVAIL:
- case GSM_SECURITY_SUCCEEDED:
- rc = finish_lu(conn);
- break;
-
- default:
- rc = -EINVAL;
- };
-
- return rc;
-}
-
-static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- if (!conn->loc_operation)
- return 0;
-
- if (authorize_subscriber(conn->loc_operation, conn->subscr))
- return gsm48_secure_channel(conn,
- conn->loc_operation->key_seq,
- _gsm0408_authorize_sec_cb, NULL);
- return 0;
-}
-
+/* Clear Requeest was received from MSC, release all transactions */
void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
{
struct gsm_trans *trans, *temp;
@@ -408,7 +263,7 @@ void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t caus
* Cancel any outstanding location updating request
* operation taking place on the subscriber connection.
*/
- release_loc_updating_req(conn, 0);
+ //release_loc_updating_req(conn, 1);
/* We might need to cancel the paging response or such. */
if (conn->sec_operation && conn->sec_operation->cb) {
@@ -440,6 +295,7 @@ restart:
}
}
+/* clear all transactions gloablly; used in case of MNCC socket disconnect */
void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
{
struct gsm_trans *trans, *temp;
@@ -497,15 +353,26 @@ static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn)
bts->network->network_code, bts->location_area_code);
if (conn->subscr->tmsi == GSM_RESERVED_TMSI) {
+ /* we did not allocate a TMSI to the MS, so we need to
+ * include the IMSI in order for the MS to delete any
+ * old TMSI that might still be allocated */
uint8_t mi[10];
int len;
len = gsm48_generate_mid_from_imsi(mi, conn->subscr->imsi);
mid = msgb_put(msg, len);
memcpy(mid, mi, len);
} else {
+ /* Include the TMSI, which means that the MS will send a
+ * TMSI REALLOCATION COMPLETE, and we should wait for
+ * that until T3250 expiration */
mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
gsm48_generate_mid_from_tmsi(mid, conn->subscr->tmsi);
}
+ /* TODO: Follow-on proceed */
+ /* TODO: CTS permission */
+ /* TODO: Equivalent PLMNs */
+ /* TODO: Emergency Number List */
+ /* TODO: Per-MS T3312 */
DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
@@ -530,27 +397,10 @@ static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, uint8_t id
return gsm48_conn_sendmsg(msg, conn, NULL);
}
-static struct gsm_subscriber *subscr_create(const struct gsm_network *net,
- const char *imsi)
-{
- if (net->subscr_creation_mode == GSM_SUBSCR_DONT_CREATE)
- return NULL;
-
- if (net->subscr_creation_mode & GSM_SUBSCR_CREAT_W_REGEXP)
- if (!subscr_regexp_check(net, imsi))
- return NULL;
-
- return subscr_create_subscriber(net->subscr_group, imsi, net->ext_min,
- net->ext_max);
-}
-
/* Parse Chapter 9.2.11 Identity Response */
static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
struct gsm48_hdr *gh = msgb_l3(msg);
- struct gsm_lchan *lchan = msg->lchan;
- struct gsm_bts *bts = lchan->ts->trx->bts;
- struct gsm_network *net = bts->network;
uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
char mi_string[GSM48_MI_SIZE];
@@ -560,58 +410,10 @@ static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *ms
osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
- switch (mi_type) {
- case GSM_MI_TYPE_IMSI:
- /* look up subscriber based on IMSI, create if not found */
- if (!conn->subscr) {
- conn->subscr = subscr_get_by_imsi(net->subscr_group,
- mi_string);
- if (!conn->subscr)
- conn->subscr = subscr_create(net, mi_string);
- }
- if (!conn->subscr && conn->loc_operation) {
- gsm0408_loc_upd_rej(conn, bts->network->reject_cause);
- release_loc_updating_req(conn, 1);
- return 0;
- }
- if (conn->loc_operation)
- conn->loc_operation->waiting_for_imsi = 0;
- break;
- case GSM_MI_TYPE_IMEI:
- case GSM_MI_TYPE_IMEISV:
- /* update subscribe <-> IMEI mapping */
- if (conn->subscr) {
- db_subscriber_assoc_imei(conn->subscr, mi_string);
- db_sync_equipment(&conn->subscr->equipment);
- }
- if (conn->loc_operation)
- conn->loc_operation->waiting_for_imei = 0;
- break;
- }
-
- /* Check if we can let the mobile station enter */
- return gsm0408_authorize(conn, msg);
-}
-
-
-static void loc_upd_rej_cb(void *data)
-{
- struct gsm_subscriber_connection *conn = data;
- struct gsm_lchan *lchan = conn->lchan;
- struct gsm_bts *bts = lchan->ts->trx->bts;
-
- LOGP(DMM, LOGL_DEBUG, "Location Updating Request procedure timedout.\n");
- gsm0408_loc_upd_rej(conn, bts->network->reject_cause);
- release_loc_updating_req(conn, 1);
-}
-
-static void schedule_reject(struct gsm_subscriber_connection *conn)
-{
- conn->loc_operation->updating_timer.cb = loc_upd_rej_cb;
- conn->loc_operation->updating_timer.data = conn;
- osmo_timer_schedule(&conn->loc_operation->updating_timer, 5, 0);
+ return vlr_sub_rx_id_resp(conn->subscr, gh->data+1, gh->data[0]);
}
+/* FIXME: to libosmogsm */
static const struct value_string lupd_names[] = {
{ GSM48_LUPD_NORMAL, "NORMAL" },
{ GSM48_LUPD_PERIODIC, "PERIODIC" },
@@ -628,6 +430,11 @@ static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb
struct gsm_bts *bts = conn->bts;
uint8_t mi_type;
char mi_string[GSM48_MI_SIZE];
+ enum vlr_lu_type vlr_lu_type = VLR_LU_TYPE_REGULAR;
+
+ uint32_t tmsi;
+ char *imsi;
+ struct osmo_location_area_id old_lai, new_lai;
lu = (struct gsm48_loc_upd_req *) gh->data;
@@ -643,75 +450,47 @@ static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb
switch (lu->type) {
case GSM48_LUPD_NORMAL:
osmo_counter_inc(bts->network->stats.loc_upd_type.normal);
+ vlr_lu_type = VLR_LU_TYPE_REGULAR;
break;
case GSM48_LUPD_IMSI_ATT:
osmo_counter_inc(bts->network->stats.loc_upd_type.attach);
+ vlr_lu_type = VLR_LU_TYPE_IMSI_ATTACH;
break;
case GSM48_LUPD_PERIODIC:
osmo_counter_inc(bts->network->stats.loc_upd_type.periodic);
+ vlr_lu_type = VLR_LU_TYPE_PERIODIC;
break;
}
- /*
- * Pseudo Spoof detection: Just drop a second/concurrent
- * location updating request.
- */
- if (conn->loc_operation) {
- DEBUGPC(DMM, "ignoring request due an existing one: %p.\n",
- conn->loc_operation);
- gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR);
- return 0;
- }
-
- allocate_loc_updating_req(conn);
-
- conn->loc_operation->key_seq = lu->key_seq;
+ /* TODO: 10.5.1.6 MS Classmark for UMTS / Classmark 2 */
+ /* TODO: 10.5.3.14 Aditional update parameters (CS fallback calls) */
+ /* TODO: 10.5.7.8 Device properties */
+ /* TODO: 10.5.1.15 MS network feature support */
switch (mi_type) {
case GSM_MI_TYPE_IMSI:
- DEBUGPC(DMM, "\n");
- /* we always want the IMEI, too */
- mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
- conn->loc_operation->waiting_for_imei = 1;
-
- /* look up subscriber based on IMSI, create if not found */
- subscr = subscr_get_by_imsi(bts->network->subscr_group, mi_string);
- if (!subscr)
- subscr = subscr_create(bts->network, mi_string);
-
- if (!subscr) {
- gsm0408_loc_upd_rej(conn, bts->network->reject_cause);
- release_loc_updating_req(conn, 0);
- return 0;
- }
+ tmsi = GSM_RESERVED_TMSI;
+ imsi = mi_string;
break;
case GSM_MI_TYPE_TMSI:
- DEBUGPC(DMM, "\n");
- /* look up the subscriber based on TMSI, request IMSI if it fails */
- subscr = subscr_get_by_tmsi(bts->network->subscr_group,
- tmsi_from_string(mi_string));
- if (!subscr) {
- /* send IDENTITY REQUEST message to get IMSI */
- mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI);
- conn->loc_operation->waiting_for_imsi = 1;
- }
- /* we always want the IMEI, too */
- mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
- conn->loc_operation->waiting_for_imei = 1;
- break;
- case GSM_MI_TYPE_IMEI:
- case GSM_MI_TYPE_IMEISV:
- /* no sim card... FIXME: what to do ? */
- DEBUGPC(DMM, "unimplemented mobile identity type\n");
+ tmsi = tmsi_from_string(mi_string);
+ imsi = NULL;
break;
- default:
+ default:
DEBUGPC(DMM, "unknown mobile identity type\n");
+ tmsi = GSM_RESERVED_TMSI;
+ imsi = NULL;
break;
}
- /* schedule the reject timer */
- schedule_reject(conn);
+ gsm48_decode_lai(&lu->lai, &old_lai.plmn.mcc,
+ &old_lai.plmn.mnc, &old_lai.lac);
+ new_lai.plmn.mcc = bts->network->country_code;
+ new_lai.plmn.mnc = bts->network->network_code;
+ new_lai.lac = bts->location_area_code;
+ subscr = vlr_loc_update(g_vlr, conn, vlr_lu_type, tmsi, imsi,
+ &old_lai, &new_lai);
if (!subscr) {
DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
/* FIXME: request id? close channel? */
@@ -721,12 +500,11 @@ static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb
conn->subscr = subscr;
conn->subscr->equipment.classmark1 = lu->classmark1;
- /* check if we can let the subscriber into our network immediately
- * or if we need to wait for identity responses. */
- return gsm0408_authorize(conn, msg);
+ return 0;
}
/* Turn int into semi-octet representation: 98 => 0x89 */
+/* FIXME: libosmocore/libosmogsm */
static uint8_t bcdify(uint8_t value)
{
uint8_t ret;
@@ -1124,39 +902,19 @@ static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct
{
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data;
- struct gsm_network *net = conn->bts->network;
+ bool is_r99 = false; /* FIXME */
DEBUGP(DMM, "MM AUTHENTICATION RESPONSE (sres = %s): ",
osmo_hexdump(ar->sres, 4));
- /* Safety check */
- if (!conn->sec_operation) {
- DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n");
- return -EIO;
- }
-
- /* Validate SRES */
- if (memcmp(conn->sec_operation->atuple.vec.sres, ar->sres,4)) {
- int rc;
- gsm_cbfn *cb = conn->sec_operation->cb;
-
- DEBUGPC(DMM, "Invalid (expected %s)\n",
- osmo_hexdump(conn->sec_operation->atuple.vec.sres, 4));
-
- if (cb)
- cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
- NULL, conn, conn->sec_operation->cb_data);
-
- rc = gsm48_tx_mm_auth_rej(conn);
- release_security_operation(conn);
- return rc;
- }
-
- DEBUGPC(DMM, "OK\n");
+ return vlr_sub_rx_auth_resp(conn->subscr, is_r99, false, ar->sres, 4);
+}
- /* Start ciphering */
- return gsm0808_cipher_mode(conn, net->a5_encryption,
- conn->sec_operation->atuple.vec.kc, 8, 0);
+static int gsm48_rx_mm_tmsi_reall_compl(struct gsm_subscriber_connection *conn)
+{
+ DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+ subscr_name(conn->subscr));
+ return vlr_sub_rx_tmsi_realloc_req(conn);
}
/* Receive a GSM 04.08 Mobility Management (MM) message */
@@ -1180,9 +938,7 @@ static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *m
rc = gsm48_rx_mm_status(msg);
break;
case GSM48_MT_MM_TMSI_REALL_COMPL:
- DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
- subscr_name(conn->subscr));
- release_loc_updating_req(conn, 1);
+ rc = gsm48_rx_mm_tmsi_reall_compl(conn);
break;
case GSM48_MT_MM_IMSI_DETACH_IND:
rc = gsm48_rx_mm_imsi_detach_ind(conn, msg);
@@ -3707,6 +3463,78 @@ int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
return rc;
}
+/***********************************************************************
+ * VLR integration
+ ***********************************************************************/
+
+/* VLR asks us to send an authentication request */
+static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct gsm_auth_tuple *at)
+{
+ struct gsm_subscriber_connection *conn = msc_conn_ref;
+ return gsm48_tx_mm_auth_req(conn, at->vec.rand, at->key_seq);
+}
+
+/* VLR asks us to send an authentication reject */
+static int msc_vlr_tx_auth_rej(void *msc_conn_ref)
+{
+ struct gsm_subscriber_connection *conn = msc_conn_ref;
+ return gsm48_tx_mm_auth_rej(conn);
+}
+
+/* VLR asks us to transmit an Identity Request of given type */
+static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type)
+{
+ struct gsm_subscriber_connection *conn = msc_conn_ref;
+ return mm_tx_identity_req(conn, mi_type);
+}
+
+/* VLR asks us to transmit a Location Update Accept */
+static int msc_vlr_tx_lu_ack(void *msc_conn_ref)
+{
+ struct gsm_subscriber_connection *conn = msc_conn_ref;
+ return gsm0408_loc_upd_acc(conn);
+}
+
+/* VLR asks us to transmit a Location Update Reject */
+static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause)
+{
+ struct gsm_subscriber_connection *conn = msc_conn_ref;
+ return gsm0408_loc_upd_rej(conn, cause);
+}
+
+/* VLR asks us to start using ciphering */
+static int msc_vlr_set_ciph_mode(void *msc_conn_ref)
+{
+ struct gsm_subscriber_connection *conn = msc_conn_ref;
+#if 0
+ struct vlr_subscriber *vsub = conn->subscr;
+ struct gms_auth_tuple *tuple = subscr->last_tuple;
+
+ if (!tuple)
+ return -1;
+
+ return gsm0808_cipher_mode(conn, subscr->net->a5_encryption,
+ tuple->vec.kc, 8, 0);
+#endif
+}
+
+/* VLR informs us that the subscriber data has somehow been modified */
+static void msc_vlr_subscr_update(struct vlr_subscriber *subscr)
+{
+ /* FIXME */
+}
+
+/* operations that we need to implement for libvlr */
+static const struct vlr_ops msc_vlr_ops = {
+ .tx_auth_req = msc_vlr_tx_auth_req,
+ .tx_auth_rej = msc_vlr_tx_auth_rej,
+ .tx_id_req = msc_vlr_tx_id_req,
+ .tx_lu_ack = msc_vlr_tx_lu_ack,
+ .tx_lu_rej = msc_vlr_tx_lu_rej,
+ .set_ciph_mode = msc_vlr_set_ciph_mode,
+ .subscr_update = msc_vlr_subscr_update,
+};
+
/*
* This will be ran by the linker when loading the DSO. We use it to
* do system initialization, e.g. registration of signal handlers.
@@ -3714,4 +3542,6 @@ int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
static __attribute__((constructor)) void on_dso_load_0408(void)
{
osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, NULL);
+
+ g_vlr = vlr_init(tall_bsc_ctx, &msc_vlr_ops, NULL, 0);
}
diff --git a/openbsc/src/libmsc/gsm_04_11.c b/openbsc/src/libmsc/gsm_04_11.c
index 5ee13452b..45a260a0b 100644
--- a/openbsc/src/libmsc/gsm_04_11.c
+++ b/openbsc/src/libmsc/gsm_04_11.c
@@ -1018,6 +1018,7 @@ void _gsm411_sms_trans_free(struct gsm_trans *trans)
}
}
+/* Process incoming SAPI N-REJECT from BSC */
void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
{
struct gsm_network *net;
diff --git a/openbsc/src/libmsc/osmo_msc.c b/openbsc/src/libmsc/osmo_msc.c
index f12aac385..96b1c2bcf 100644
--- a/openbsc/src/libmsc/osmo_msc.c
+++ b/openbsc/src/libmsc/osmo_msc.c
@@ -28,6 +28,7 @@
#include <openbsc/gsm_04_11.h>
+/* Receive a SAPI-N-REJECT from BSC */
static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
{
int sapi = dlci & 0x7;
@@ -36,12 +37,14 @@ static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
gsm411_sapi_n_reject(conn);
}
+/* Receive a CLEAR REQUEST from BSC */
static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
{
gsm0408_clear_request(conn, cause);
return 1;
}
+/* Receive a COMPLETE LAYER3 INFO from BSC */
static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
uint16_t chosen_channel)
{
@@ -64,11 +67,13 @@ static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg
return BSC_API_CONN_POL_REJECT;
}
+/* Receive a DTAP message from BSC */
static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
{
gsm0408_dispatch(conn, msg);
}
+/* Receive an ASSIGNMENT COMPLETE from BSC */
static void msc_assign_compl(struct gsm_subscriber_connection *conn,
uint8_t rr_cause, uint8_t chosen_channel,
uint8_t encr_alg_id, uint8_t speec)
@@ -76,12 +81,14 @@ static void msc_assign_compl(struct gsm_subscriber_connection *conn,
LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n");
}
+/* Receive an ASSIGNMENT FAILURE from BSC */
static void msc_assign_fail(struct gsm_subscriber_connection *conn,
uint8_t cause, uint8_t *rr_cause)
{
LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n");
}
+/* Receive a CLASSMARK CHNAGE from BSC */
static void msc_classmark_chg(struct gsm_subscriber_connection *conn,
const uint8_t *cm2, uint8_t cm2_len,
const uint8_t *cm3, uint8_t cm3_len)
@@ -99,6 +106,7 @@ static void msc_classmark_chg(struct gsm_subscriber_connection *conn,
}
}
+/* Receive a CIPHERING MODE COMPLETE from BSC */
static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn,
struct msgb *msg, uint8_t alg_id)
{
@@ -127,7 +135,7 @@ static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn,
}
-
+/* MSC-level operations to be called by libbsc in NITB */
static struct bsc_api msc_handler = {
.sapi_n_reject = msc_sapi_n_reject,
.compl_l3 = msc_compl_l3,
@@ -171,6 +179,11 @@ static void msc_release_connection(struct gsm_subscriber_connection *conn)
subscr_update_expire_lu(conn->subscr, conn->bts);
conn->in_release = 1;
+
+ /* Let VLR know that a given subscriber is gone */
+ if (conn->subscr)
+ vlr_sub_disconnected(conn->subscr);
+
gsm0808_clear(conn);
subscr_con_free(conn);
}
diff --git a/openbsc/src/libmsc/transaction.c b/openbsc/src/libmsc/transaction.c
index 78815614c..e641703d3 100644
--- a/openbsc/src/libmsc/transaction.c
+++ b/openbsc/src/libmsc/transaction.c
@@ -33,6 +33,12 @@ void *tall_trans_ctx;
void _gsm48_cc_trans_free(struct gsm_trans *trans);
+/* Find a transaction in connection for given protocol + transaction ID
+ * \param[in] conn Connection in whihc we want to find transaction
+ * \param[in] proto Protocol of transaction
+ * \param[in] trans_id Transaction ID of transaction
+ * \returns Matching transaction, if any
+ */
struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn,
uint8_t proto, uint8_t trans_id)
{
@@ -49,6 +55,11 @@ struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn,
return NULL;
}
+/* Find a transaction by call reference
+ * \param[in] net Network in which we should search
+ * \param[in] callref Call Reference of transaction
+ * \returns Matching transaction, if any
+ */
struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
uint32_t callref)
{
@@ -61,6 +72,13 @@ struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
return NULL;
}
+/*! Allocate a new transaction and add it to network list
+ * \param[in] net Netwokr in which we allocate transaction
+ * \param[in] subscr Subscriber for which we allocate transaction
+ * \param[in] protocol Protocol (CC/SMS/...)
+ * \param[in] callref Call Reference
+ * \returns Transaction
+ */
struct gsm_trans *trans_alloc(struct gsm_network *net,
struct gsm_subscriber *subscr,
uint8_t protocol, uint8_t trans_id,
@@ -87,6 +105,9 @@ struct gsm_trans *trans_alloc(struct gsm_network *net,
return trans;
}
+/* Release a transaction
+ * \param[in] trans Transaction to be released
+ */
void trans_free(struct gsm_trans *trans)
{
switch (trans->protocol) {
@@ -118,7 +139,12 @@ void trans_free(struct gsm_trans *trans)
}
/* allocate an unused transaction ID for the given subscriber
- * in the given protocol using the ti_flag specified */
+ * in the given protocol using the ti_flag specified
+ * \param[in] net GSM network
+ * \param[in] subscr Subscriber for which to find ID
+ * \param[in] protocol Protocol for whihc to find ID
+ * \param[in] ti_flag FIXME
+ */
int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr,
uint8_t protocol, uint8_t ti_flag)
{
@@ -151,6 +177,10 @@ int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr
return -1;
}
+/* Check if we have any transaction for given connection
+ * \param[in] conn Connection to check
+ * \returns 1 in case there is a transaction, 0 otherwise
+ */
int trans_has_conn(const struct gsm_subscriber_connection *conn)
{
struct gsm_trans *trans;
diff --git a/openbsc/src/libvlr/Makefile.am b/openbsc/src/libvlr/Makefile.am
new file mode 100644
index 000000000..462f17148
--- /dev/null
+++ b/openbsc/src/libvlr/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) \
+ $(COVERAGE_CFLAGS) $(LIBCRYPTO_CFLAGS)
+
+noinst_HEADERS = vlr_auth_fsm.h vlr_core.h vlr_lu_fsm.h vlr_access_req_fsm.h
+
+noinst_LIBRARIES = libvlr.a
+
+libvlr_a_SOURCES = vlr.c vlr_auth_fsm.c vlr_lu_fsm.c vlr_access_req_fsm.c
diff --git a/openbsc/src/libvlr/vlr.c b/openbsc/src/libvlr/vlr.c
new file mode 100644
index 000000000..9edfc0acd
--- /dev/null
+++ b/openbsc/src/libvlr/vlr.c
@@ -0,0 +1,795 @@
+/* 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/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/apn.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gprs_gsup_client.h>
+#include <openbsc/vlr.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/debug.h>
+
+#include <openssl/rand.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+#include "vlr_access_req_fsm.h"
+#include "vlr_lu_fsm.h"
+
+#define SGSN_SUBSCR_MAX_RETRIES 3
+#define SGSN_SUBSCR_RETRY_INTERVAL 10
+
+/***********************************************************************
+ * Convenience functions
+ ***********************************************************************/
+
+uint32_t vlr_timer(struct vlr_instance *vlr, uint32_t timer)
+{
+ uint32_t tidx = 0xffffffff;
+
+ switch (timer) {
+ case 3270:
+ tidx = VLR_T_3270;
+ break;
+ case 3260:
+ tidx = VLR_T_3260;
+ break;
+ case 3250:
+ tidx = VLR_T_3250;
+ break;
+ }
+
+ OSMO_ASSERT(tidx < sizeof(vlr->cfg.timer));
+ return vlr->cfg.timer[tidx];
+}
+
+/* return static buffer with printable name of VLR subscriber */
+const char *vlr_sub_name(struct vlr_subscriber *vsub)
+{
+ static char buf[32];
+ if (vsub->imsi[0])
+ strncpy(buf, vsub->imsi, sizeof(buf));
+ else
+ snprintf(buf, sizeof(buf), "0x%08x", vsub->tmsi);
+ buf[sizeof(buf)-1] = '\0';
+ return buf;
+}
+
+struct vlr_subscriber *
+vlr_subscr_find_by_imsi(struct vlr_instance *vlr, const char *imsi)
+{
+ struct vlr_subscriber *vsub;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (!strcmp(vsub->imsi, imsi))
+ return vsub;
+ }
+ return NULL;
+}
+
+struct vlr_subscriber *
+vlr_subscr_find_by_tmsi(struct vlr_instance *vlr, uint32_t tmsi)
+{
+ struct vlr_subscriber *vsub;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vsub->tmsi == tmsi)
+ return vsub;
+ }
+ return NULL;
+}
+
+/* Transmit GSUP message to HLR */
+static int vlr_tx_gsup_message(struct vlr_instance *vlr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct msgb *msg = gprs_gsup_msgb_alloc();
+
+ osmo_gsup_encode(msg, gsup_msg);
+
+ if (!vlr->gsup_client) {
+ LOGP(DVLR, LOGL_NOTICE, "GSUP link is down, cannot "
+ "send GSUP: %s\n", msgb_hexdump(msg));
+ msgb_free(msg);
+ return -ENOTSUP;
+ }
+
+ LOGP(DVLR, LOGL_DEBUG,
+ "Sending GSUP, will send: %s\n", msgb_hexdump(msg));
+
+ return gprs_gsup_client_send(vlr->gsup_client, msg);
+}
+
+/* Transmit GSUP message for subscriber to HLR, using IMSI from subscriber */
+static int vlr_subscr_tx_gsup_message(struct vlr_subscriber *vsub,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct vlr_instance *vlr = vsub->vlr;
+
+ if (strlen(gsup_msg->imsi) == 0)
+ strncpy(gsup_msg->imsi, vsub->imsi, sizeof(gsup_msg->imsi) - 1);
+
+ return vlr_tx_gsup_message(vlr, gsup_msg);
+}
+
+/* Transmit GSUP error in response to original message */
+static int vlr_tx_gsup_error_reply(struct vlr_instance *vlr,
+ struct osmo_gsup_message *gsup_orig,
+ enum gsm48_gmm_cause cause)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+
+ strncpy(gsup_reply.imsi, gsup_orig->imsi, sizeof(gsup_reply.imsi) - 1);
+ gsup_reply.cause = cause;
+ gsup_reply.message_type =
+ OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type);
+
+ return vlr_tx_gsup_message(vlr, &gsup_reply);
+}
+
+/* Allocate a new subscriber and insert it into list */
+struct vlr_subscriber *vlr_sub_alloc(struct vlr_instance *vlr)
+{
+ struct vlr_subscriber *vsub;
+ int i;
+
+ vsub = talloc_zero(vlr, struct vlr_subscriber);
+ vsub->vlr = vlr;
+ vsub->tmsi = GSM_RESERVED_TMSI;
+
+ for (i = 0; i < ARRAY_SIZE(vsub->auth_tuples); i++)
+ vsub->auth_tuples[i].key_seq = GSM_KEY_SEQ_INVAL;
+
+ INIT_LLIST_HEAD(&vsub->ps.pdp_list);
+
+ llist_add_tail(&vsub->list, &vlr->subscribers);
+
+ /* Do not add to list of subscribers yet, as we don't yet want this
+ * subscriber to be found by lookup from e.g. MT-SMS or MT-call
+ * delivery yet. */
+
+ return vsub;
+}
+
+static int vlr_sub_purge(struct vlr_subscriber *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST;
+
+ /* provide HLR number in case we know it */
+ gsup_msg.hlr_enc_len = vsub->hlr.len;
+ gsup_msg.hlr_enc = vsub->hlr.buf;
+
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+}
+
+void vlr_sub_cleanup(struct vlr_subscriber *vsub)
+{
+ if (vsub->flags & GPRS_SUBSCRIBER_ENABLE_PURGE) {
+ vlr_sub_purge(vsub);
+ vsub->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE;
+ }
+}
+
+void vlr_sub_cancel(struct vlr_subscriber *vsub)
+{
+ vsub->authorized = 0;
+ vsub->flags |= GPRS_SUBSCRIBER_CANCELLED;
+ vsub->flags &= ~GPRS_SUBSCRIBER_ENABLE_PURGE;
+
+ vsub->vlr->ops.subscr_update(vsub);
+ vlr_sub_cleanup(vsub);
+}
+
+int vlr_sub_alloc_tmsi(struct vlr_subscriber *vsub)
+{
+ struct vlr_instance *vlr = vsub->vlr;
+ uint32_t tmsi;
+
+ for (;;) {
+ if (RAND_bytes((uint8_t *) &tmsi, sizeof(tmsi)) != 1) {
+ LOGP(DVLR, LOGL_ERROR, "RAND_bytes failed\n");
+ return -1;
+ }
+ /* 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 |= 0xC000000;
+ } else {
+ /* MSC */
+ if ((tmsi & 0xC0000000) == 0xC0000000)
+ tmsi &= ~0xC0000000;
+ }
+
+ if (!vlr_subscr_find_by_tmsi(vlr, tmsi)) {
+ vsub->tmsi = tmsi;
+ return 0;
+ }
+ }
+
+ /* not reached */
+ return -1;
+}
+
+/***********************************************************************
+ * PDP context data
+ ***********************************************************************/
+
+struct sgsn_subscriber_pdp_data *
+vlr_sub_pdp_data_alloc(struct vlr_subscriber *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);
+
+ return pdata;
+}
+
+static int vlr_sub_pdp_data_clear(struct vlr_subscriber *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);
+ talloc_free(pdp);
+ count += 1;
+ }
+
+ return count;
+}
+
+static struct sgsn_subscriber_pdp_data *
+vlr_sub_pdp_data_get_by_id(struct vlr_subscriber *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,
+ struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) {
+ vlr_tx_gsup_error_reply(vlr, gsup_msg,
+ GMM_CAUSE_IMSI_UNKNOWN);
+ LOGP(DVLR, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP request "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ } else if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGP(DVLR, 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 {
+ LOGP(DVLR, 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,
+ 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_sub_req_lu(struct vlr_subscriber *vsub, bool is_ps)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+ int rc;
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
+ rc = vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+
+ return rc;
+}
+
+/* VLR internal call to request tuples from HLR */
+int vlr_sub_req_sai(struct vlr_subscriber *vsub,
+ const uint8_t *auts, const uint8_t *auts_rand)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+ gsup_msg.auts = auts;
+ gsup_msg.rand = auts_rand;
+
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+}
+
+/* Tell HLR that authentication failure occurred */
+int vlr_sub_tx_auth_fail_rep(struct vlr_subscriber *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_AUTH_FAIL_REPORT;
+ strncpy(gsup_msg.imsi, vsub->imsi, sizeof(gsup_msg.imsi) - 1);
+ return vlr_tx_gsup_message(vsub->vlr, &gsup_msg);
+}
+
+/* Update the subscriber with GSUP-received auth tuples */
+void vlr_sub_update_tuples(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ unsigned int i;
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "Adding %zu auth tuples\n",
+ gsup->num_auth_vectors);
+
+ 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 = GSM_KEY_SEQ_INVAL;
+ }
+
+ 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 wih invalid cksn %zu\n",
+ key_seq);
+ continue;
+ }
+ vsub->auth_tuples[i].vec = gsup->auth_vectors[i];
+ vsub->auth_tuples[i].key_seq = key_seq;
+ }
+
+ vsub->auth_tuples_updated = true;
+ vsub->auth_error_cause = SGSN_ERROR_CAUSE_NONE;
+}
+
+/* Handle SendAuthInfo Result/Error from HLR */
+static int vlr_sub_handle_sai_res(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct osmo_fsm_inst *auth_fi = vsub->auth_fsm;
+ void *data = (void *) gsup;
+
+ 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_sub_gsup_insert_data(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ unsigned idx;
+ int rc;
+
+ if (gsup_msg->msisdn_enc) {
+ gsm48_mi_to_string(vsub->msisdn, sizeof(vsub->msisdn),
+ gsup_msg->msisdn_enc,
+ gsup_msg->msisdn_enc_len);
+ }
+
+ if (gsup_msg->hlr_enc) {
+ if (gsup_msg->hlr_enc_len > sizeof(vsub->hlr.buf)) {
+ LOGP(DVLR, 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_sub_pdp_data_clear(vsub);
+ if (rc > 0)
+ LOGP(DVLR, 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_sub_pdp_data_get_by_id(vsub, ctx_id);
+ if (!pdp_data) {
+ pdp_data = vlr_sub_pdp_data_alloc(vsub);
+ pdp_data->context_id = ctx_id;
+ }
+
+ OSMO_ASSERT(pdp_data != NULL);
+ pdp_data->pdp_type = pdp_info->pdp_type;
+ 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_sub_handle_isd_req(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+
+ vlr_sub_gsup_insert_data(vsub, gsup);
+
+ vsub->authorized = 1;
+ vsub->auth_error_cause = SGSN_ERROR_CAUSE_NONE;
+ vsub->flags |= GPRS_SUBSCRIBER_ENABLE_PURGE;
+ 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_sub_handle_lu_res(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ if (!vsub->lu_fsm) {
+ 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_sub_gsup_insert_data(vsub, gsup);
+
+ vsub->authorized = 1;
+ vsub->auth_error_cause = SGSN_ERROR_CAUSE_NONE;
+ vsub->flags |= GPRS_SUBSCRIBER_ENABLE_PURGE;
+ vsub->vlr->ops.subscr_update(vsub);
+
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES, NULL);
+
+ return 0;
+}
+
+/* Handle UpdateLocation Result from HLR */
+static int vlr_sub_handle_lu_err(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ if (!vsub->lu_fsm) {
+ 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));
+
+ vsub->authorized = 0;
+ vsub->auth_error_cause = gsup->cause;
+
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES,
+ (void *)&gsup->cause);
+
+ return 0;
+}
+
+/* Handel LOCATION CANCEL request from HLR */
+static int vlr_sub_handle_cancel_req(struct vlr_subscriber *vsub,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+ int is_update_procedure = !gsup_msg->cancel_type ||
+ gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE;
+
+ LOGVSUBP(LOGL_INFO, vsub, "Cancelling MS subscriber (%s)\n",
+ is_update_procedure ?
+ "update procedure" : "subscription withdraw");
+
+ gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT;
+ vlr_subscr_tx_gsup_message(vsub, &gsup_reply);
+
+ if (is_update_procedure)
+ vsub->auth_error_cause = SGSN_ERROR_CAUSE_NONE;
+ else
+ /* Since a withdraw cause is not specified, just abort the
+ * current attachment. The following re-attachment should then
+ * be rejected with a proper cause value.
+ */
+ vsub->auth_error_cause = GMM_CAUSE_IMPL_DETACHED;
+
+ vlr_sub_cancel(vsub);
+
+ return 0;
+}
+
+/* Incoming handler for GSUP from HLR */
+static int vlr_gsupc_read_cb(struct gprs_gsup_client *gsupc, struct msgb *msg)
+{
+ struct vlr_instance *vlr = (struct vlr_instance *) gsupc->data;
+ struct vlr_subscriber *vsub;
+ struct osmo_gsup_message gsup;
+ int rc;
+
+ rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
+ if (rc < 0) {
+ LOGP(DVLR, LOGL_ERROR,
+ "decoding GSUP message fails with error '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+ return rc;
+ }
+
+ if (!gsup.imsi[0]) {
+ LOGP(DVLR, LOGL_ERROR, "Missing IMSI in GSUP message\n");
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup.message_type))
+ vlr_tx_gsup_error_reply(vlr, &gsup,
+ GMM_CAUSE_INV_MAND_INFO);
+ return -GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ vsub = vlr_subscr_find_by_imsi(vlr, gsup.imsi);
+ if (!vsub) {
+ switch (gsup.message_type) {
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ return vlr_rx_gsup_purge_no_subscr(vlr, &gsup);
+ default:
+ return vlr_rx_gsup_unknown_imsi(vlr, &gsup);
+ }
+ }
+
+ switch (gsup.message_type) {
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ rc = vlr_sub_handle_sai_res(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+ rc = vlr_sub_handle_isd_req(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST:
+ rc = vlr_sub_handle_cancel_req(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+ rc = vlr_sub_handle_lu_res(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
+ rc = vlr_sub_handle_lu_err(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST:
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "Rx GSUP msg_type=%d not yet implemented\n",
+ gsup.message_type);
+ rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+ break;
+ default:
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "Rx GSUP msg_type=%d not valid at VLR/SGSN side\n",
+ gsup.message_type);
+ rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+ break;
+ }
+
+ return rc;
+}
+
+/* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */
+int vlr_sub_rx_id_resp(struct vlr_subscriber *vsub,
+ const uint8_t *mi, size_t mi_len)
+{
+ char mi_string[GSM48_MI_SIZE];
+ uint8_t mi_type = mi[0] & GSM_MI_TYPE_MASK;
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+
+ /* update the vlr_subscriber with the given identity */
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ if (vsub->imsi[0] && strcmp(vsub->imsi, 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';
+ }
+ break;
+ case GSM_MI_TYPE_IMEI:
+ strncpy(vsub->imei, mi_string, sizeof(vsub->imei));
+ vsub->imei[sizeof(vsub->imei)-1] = '\0';
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ strncpy(vsub->imeisv, mi_string, sizeof(vsub->imeisv));
+ vsub->imeisv[sizeof(vsub->imeisv)-1] = '\0';
+ break;
+ }
+
+ if (vsub->auth_fsm) {
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ osmo_fsm_inst_dispatch(vsub->auth_fsm,
+ VLR_AUTH_E_MS_ID_IMSI, mi_string);
+ break;
+ }
+ }
+
+ if (vsub->lu_fsm) {
+ uint32_t event = 0;
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ event = VLR_ULA_E_ID_IMSI;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ event = VLR_ULA_E_ID_IMEI;
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ event = VLR_ULA_E_ID_IMEISV;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, event, mi_string);
+ } else {
+ LOGVSUBP(LOGL_NOTICE, vsub, "gratuitous ID RESPONSE?!?\n");
+ }
+
+ return 0;
+}
+
+/* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */
+void vlr_sub_rx_tmsi_reall_compl(struct vlr_subscriber *vsub)
+{
+ if (vsub->lu_fsm) {
+ osmo_fsm_inst_dispatch(vsub->lu_fsm,
+ VLR_ULA_E_NEW_TMSI_ACK, NULL);
+ } else if (vsub->proc_arq_fsm) {
+ osmo_fsm_inst_dispatch(vsub->proc_arq_fsm,
+ PR_ARQ_E_TMSI_ACK, NULL);
+ } else {
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "gratuitous TMSI REALLOC COMPL");
+ }
+}
+
+struct vlr_instance *vlr_init(void *ctx, const struct vlr_ops *ops,
+ const char *addr_str, uint16_t port)
+{
+ struct vlr_instance *vlr = talloc_zero(ctx, struct vlr_instance);
+ OSMO_ASSERT(vlr);
+
+ vlr->gsup_client = gprs_gsup_client_create(addr_str, port, &vlr_gsupc_read_cb, NULL);
+ if (!vlr->gsup_client) {
+ talloc_free(vlr);
+ return NULL;
+ }
+ vlr->gsup_client->data = vlr;
+
+ INIT_LLIST_HEAD(&vlr->subscribers);
+ INIT_LLIST_HEAD(&vlr->operations);
+ memcpy(&vlr->ops, ops, sizeof(vlr->ops));
+
+ /* osmo_auth_fsm.c */
+ osmo_fsm_register(&vlr_auth_fsm);
+ /* osmo_lu_fsm.c */
+ vlr_lu_fsm_init();
+
+ return vlr;
+}
+
+/* MSC->VLR: Subscribre has disconnected */
+int vlr_sub_disconnected(struct vlr_subscriber *vsub)
+{
+ /* This corresponds to a MAP-ABORT from MSC->VLR on a classic B
+ * interface */
+ osmo_fsm_inst_term(vsub->lu_fsm, OSMO_FSM_TERM_REQUEST, NULL);
+ 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_sub_rx_auth_fail(struct vlr_subscriber *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_sub_rx_auth_resp(struct vlr_subscriber *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;
+}
diff --git a/openbsc/src/libvlr/vlr_access_req_fsm.c b/openbsc/src/libvlr/vlr_access_req_fsm.c
new file mode 100644
index 000000000..1d8094623
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_access_req_fsm.c
@@ -0,0 +1,571 @@
+/* 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/gsm/gsup.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.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
+ ***********************************************************************/
+
+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_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,
+};
+
+static const struct value_string proc_arq_vlr_event_names[] = {
+ { PR_ARQ_E_START, "START" },
+ { PR_ARQ_E_ID_IMSI, "ID-IMSI-RES" },
+ { PR_ARQ_E_AUTH_RES, "AUTH-RES" },
+ { PR_ARQ_E_UPD_LOC_RES, "UPD-LOC-RES" },
+ { PR_ARQ_E_TRACE_RES, "TRACE-RES" },
+ { PR_ARQ_E_IMEI_RES, "ID-IMEI-RES" },
+ { PR_ARQ_E_PRES_RES, "SUBSCR-PRES-VLR-RES" },
+ { PR_ARQ_E_TMSI_ACK, "TMSI-REALLOC-ACK" },
+ { 0, NULL }
+};
+
+struct proc_arq_priv {
+ struct vlr_instance *vlr;
+ struct vlr_subscriber *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *ul_child_fsm;
+ struct osmo_fsm_inst *sub_pres_vlr_fsm;
+
+ enum vlr_parq_type type;
+ bool by_tmsi;
+ char imsi[16];
+ uint32_t tmsi;
+ struct osmo_location_area_id lai;
+};
+
+static void assoc_par_with_subscr(struct osmo_fsm_inst *fi, struct vlr_subscriber *vsub)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+
+ OSMO_ASSERT(vsub->proc_arq_fsm == NULL);
+ vsub->proc_arq_fsm = fi;
+ vsub->msc_conn_ref = par->msc_conn_ref;
+ par->vsub = vsub;
+ /* Tell MSC to associate this subscriber with the given
+ * connection */
+ vlr->ops.subscr_assoc(par->msc_conn_ref, par->vsub);
+}
+
+static void proc_arq_fsm_done(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause cause,
+ enum vlr_proc_arq_result res)
+{
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, cause, &res);
+}
+
+static void _proc_arq_vlr_post_imei(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ /* TODO: Identity := IMSI */
+ /* FIXME: send process acess response already now?!? */
+ if (1 /* ciphering required */) {
+ if (0 /* TODO: TMSI reallocation at access: vlr->cfg.alloc_tmsi_arq */) {
+ vlr_sub_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;
+ }
+ } else
+ proc_arq_fsm_done(fi, OSMO_FSM_TERM_REGULAR, VLR_PR_ARQ_RES_PASSED);
+}
+
+/* After Subscriber_Present_VLR */
+static void _proc_arq_vlr_post_trace(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ if (1 /* ciphering required */) {
+ vlr->ops.set_ciph_mode(par->msc_conn_ref);
+ /* Node 3 */
+ if (0 /* IMEI check required */) {
+ /* Chck_IMEI_VLR */
+ vlr->ops.tx_id_req(par->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CHECK_IMEI,
+ vlr_timer(vlr, 3270), 3270);
+ } else
+ _proc_arq_vlr_post_imei(fi);
+ } else {
+ /* Node 4 */
+ if (0 /* IMEI check required */) {
+ vlr->ops.tx_id_req(par->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CHECK_IMEI,
+ vlr_timer(vlr, 3270), 3270);
+ } else
+ _proc_arq_vlr_post_imei(fi);
+ }
+}
+
+/* After Subscriber_Present_VLR */
+static void _proc_arq_vlr_post_pres(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ 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_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ if (!vsub->sub_dataconf_by_hlr_ind) {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, OSMO_FSM_TERM_ERROR,
+ VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+ }
+ if (0 /* roaming not allowed in LA */) {
+ /* Set User Error: Roaming not allowed in this LA */
+ proc_arq_fsm_done(fi, OSMO_FSM_TERM_ERROR,
+ VLR_PR_ARQ_RES_ROAMING_NOTALLOWED);
+ }
+ 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);
+ par->sub_pres_vlr_fsm = sub_pres_vlr_fsm_start(fi, vsub,
+ PR_ARQ_E_PRES_RES);
+ return;
+ }
+ _proc_arq_vlr_post_pres(fi);
+}
+
+static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ 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);
+}
+
+/* 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_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ OSMO_ASSERT(vsub);
+
+ /* TODO: Identity IMEI -> System Failure */
+ if (1 /* auth_required */) {
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_AUTH,
+ 0, 0);
+ vsub->auth_fsm = auth_fsm_start(vsub, fi->log_level, fi,
+ PR_ARQ_E_AUTH_RES);
+ } 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_subscriber *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);
+ } else {
+ /* TMSI was included */
+ vsub = vlr_subscr_find_by_tmsi(par->vlr, par->tmsi);
+ }
+ if (vsub) {
+ assoc_par_with_subscr(fi, vsub);
+ proc_arq_vlr_fn_post_imsi(fi);
+ 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, OSMO_FSM_TERM_ERROR,
+ VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+ return;
+ } else {
+ /* TMSI was included, are we permitted to use it? */
+ if (vlr->cfg.parq_retrieve_imsi) {
+ /* Obtain_IMSI_VLR */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_OBTAIN_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ } else {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, OSMO_FSM_TERM_ERROR,
+ VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+ }
+ }
+}
+
+/* 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_subscriber *vsub;
+
+ OSMO_ASSERT(event == PR_ARQ_E_ID_IMSI);
+
+ vsub = vlr_subscr_find_by_imsi(vlr, par->imsi);
+ if (!vsub) {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, OSMO_FSM_TERM_ERROR,
+ VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+ return;
+ }
+ assoc_par_with_subscr(fi, vsub);
+ proc_arq_vlr_fn_post_imsi(fi);
+}
+
+/* Authenticate_VLR has completed */
+static void proc_arq_vlr_fn_w_auth(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ enum vlr_auth_fsm_result *res = data;
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscriber *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+ enum vlr_proc_arq_result ret = VLR_PR_ARQ_RES_UNKNOWN_ERROR;
+
+ OSMO_ASSERT(event == PR_ARQ_E_AUTH_RES);
+
+ if (res) {
+ switch (*res) {
+ case VLR_AUTH_RES_PASSED:
+ /* Node 2 */
+ _proc_arq_vlr_node2(fi);
+ return;
+ case VLR_AUTH_RES_ABORTED:
+ /* Error */
+ ret = VLR_PR_ARQ_RES_UNKNOWN_ERROR;
+ break;
+ case VLR_AUTH_RES_UNKNOWN_SUBSCR:
+ /* Set User Error: Unidentified Subscriber */
+ ret = VLR_PR_ARQ_RES_UNIDENT_SUBSCR;
+ break;
+ case VLR_AUTH_RES_AUTH_FAILED:
+ /* Set User Error: Illegal Subscriber */
+ ret = VLR_PR_ARQ_RES_ILLEGAL_SUBSCR;
+ break;
+ case VLR_AUTH_RES_PROC_ERR:
+ /* Set User Error: System failure */
+ ret = VLR_PR_ARQ_RES_SYSTEM_FAILURE;
+ break;
+ }
+ }
+ /* send process_access_req response to caller */
+ /* enter error state */
+ proc_arq_fsm_done(fi, OSMO_FSM_TERM_ERROR, ret);
+}
+
+/* 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, OSMO_FSM_TERM_REGULAR, VLR_PR_ARQ_RES_PASSED);
+}
+
+static const struct osmo_fsm_state proc_arq_vlr_states[] = {
+ [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_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),
+ .name = "INIT",
+ .action = proc_arq_vlr_fn_init,
+ },
+ [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_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),
+ .name = "WAIT-IMSI",
+ .action = proc_arq_vlr_fn_w_obt_imsi,
+ },
+ [PR_ARQ_S_WAIT_AUTH] = {
+ .in_event_mask = S(PR_ARQ_E_AUTH_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),
+ .name = "WAIT-AUTH",
+ .action = proc_arq_vlr_fn_w_auth,
+ },
+ [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),
+ .name = "WAIT-UPD-LOC-CHILD",
+ .action = proc_arq_vlr_fn_w_upd_loc,
+ },
+ [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),
+ .name = "WAIT-SUBSCR-PRES",
+ .action = proc_arq_vlr_fn_w_pres,
+ },
+ [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),
+ .name = "WAIT-TRACE",
+ .action = proc_arq_vlr_fn_w_trace,
+ },
+ [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),
+ .name = "WAIT-CHECK-IMEI",
+ .action = proc_arq_vlr_fn_w_imei,
+ },
+ [PR_ARQ_S_WAIT_TMSI_ACK] = {
+ .in_event_mask = S(PR_ARQ_E_TMSI_ACK),
+ .out_state_mask = S(PR_ARQ_S_DONE),
+ .name = "WAIT-TMSI-ACK",
+ .action = proc_arq_vlr_fn_w_tmsi,
+ },
+ [PR_ARQ_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+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 = DVLR,
+ .event_names = proc_arq_vlr_event_names,
+};
+
+struct osmo_fsm_inst *
+vlr_proc_acc_req(struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_parq_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *lai)
+{
+ struct osmo_fsm_inst *fi;
+ struct proc_arq_priv *par;
+
+ fi = osmo_fsm_inst_alloc_child(&proc_arq_vlr_fsm, parent, term_event);
+ if (!fi)
+ return NULL;
+
+ par = talloc_zero(fi, struct proc_arq_priv);
+ par->vlr = vlr;
+ par->msc_conn_ref = msc_conn_ref;
+ par->tmsi = tmsi;
+ par->type = type;
+ par->lai = *lai;
+ if (imsi) {
+ strncpy(par->imsi, imsi, sizeof(par->imsi)-1);
+ par->imsi[sizeof(par->imsi)-1] = '\0';
+ par->by_tmsi = false;
+ } else
+ par->by_tmsi = true;
+ fi->priv = par;
+
+ osmo_fsm_inst_dispatch(fi, PR_ARQ_E_START, NULL);
+
+ return fi;
+}
+
+
+#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(void)
+{
+ //osmo_fsm_register(&upd_loc_child_vlr_fsm);
+ osmo_fsm_register(&proc_arq_vlr_fsm);
+}
diff --git a/openbsc/src/libvlr/vlr_access_req_fsm.h b/openbsc/src/libvlr/vlr_access_req_fsm.h
new file mode 100644
index 000000000..25acdfbfc
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_access_req_fsm.h
@@ -0,0 +1,36 @@
+#pragma once
+
+enum vlr_proc_arq_result {
+ VLR_PR_ARQ_RES_SYSTEM_FAILURE,
+ VLR_PR_ARQ_RES_ILLEGAL_SUBSCR,
+ VLR_PR_ARQ_RES_UNIDENT_SUBSCR,
+ VLR_PR_ARQ_RES_ROAMING_NOTALLOWED,
+ VLR_PR_ARQ_RES_ILLEGAL_EQUIP,
+ VLR_PR_ARQ_RES_UNKNOWN_ERROR,
+ VLR_PR_ARQ_RES_PASSED,
+};
+
+enum proc_arq_vlr_event {
+ PR_ARQ_E_START,
+ PR_ARQ_E_ID_IMSI,
+ PR_ARQ_E_AUTH_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_CM_SERV_REQ,
+ VLR_PR_ARQ_T_PAGING_RESP,
+ /* FIXME: differentiate between services of 24.008 10.5.3.3 */
+};
+
+
+struct osmo_fsm_inst *
+vlr_proc_acc_req(struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_parq_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *lai);
+
+void vlr_parq_fsm_init(void);
diff --git a/openbsc/src/libvlr/vlr_auth_fsm.c b/openbsc/src/libvlr/vlr_auth_fsm.c
new file mode 100644
index 000000000..37d14f55b
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_auth_fsm.c
@@ -0,0 +1,495 @@
+/* Osmocom Visitor Location Register (VLR) Autentication 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/gsm/gsup.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+
+#define S(x) (1 << (x))
+
+static const struct value_string fsm_auth_event_names[] = {
+ { VLR_AUTH_E_START, "START" },
+ { VLR_AUTH_E_HLR_SAI_ACK, "HLR-SAI-ACK" },
+ { VLR_AUTH_E_HLR_SAI_NACK, "HLR-SAI-NACK" },
+ { VLR_AUTH_E_HLR_SAI_ABORT, "HLR-SAI-ABORT" },
+ { VLR_AUTH_E_MS_AUTH_RESP, "MS-AUTH-RESP" },
+ { VLR_AUTH_E_MS_AUTH_FAIL, "MS-AUTH-FAIL" },
+ { VLR_AUTH_E_MS_ID_IMSI, "MS-ID-IMSI" },
+ { 0, NULL }
+};
+
+/* private state of the auth_fsm_instance */
+struct auth_fsm_priv {
+ struct vlr_subscriber *vsub;
+ bool by_imsi;
+};
+
+/***********************************************************************
+ * Utility functions
+ ***********************************************************************/
+
+static struct gsm_auth_tuple *
+vlr_sub_get_auth_tuple(struct vlr_subscriber *vsub, unsigned int key_seq)
+{
+ unsigned int count;
+ unsigned int idx;
+ struct gsm_auth_tuple *at = NULL;
+
+ if (!vsub)
+ return NULL;
+
+ if (key_seq == GSM_KEY_SEQ_INVAL)
+ /* Start with 0 after increment moduleo 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 == GSM_KEY_SEQ_INVAL)
+ continue;
+
+ if (vsub->auth_tuples[idx].use_count == 0) {
+ at = &vsub->auth_tuples[idx];
+ at->use_count++;
+ return at;
+ }
+ }
+ return NULL;
+}
+
+static bool check_auth_resp(struct vlr_subscriber *vsub, bool is_r99,
+ bool is_utran, const uint8_t *res,
+ uint8_t res_len)
+{
+ struct gsm_auth_tuple *at = vsub->last_tuple;
+ struct osmo_auth_vector *vec = &at->vec;
+ OSMO_ASSERT(at);
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "expected res: %s\n",
+ osmo_hexdump(vec->res, vec->res_len));
+ LOGVSUBP(LOGL_DEBUG, vsub, "received res: %s\n",
+ osmo_hexdump(res, res_len));
+
+ /* RES must be present and at leat 32bit */
+ if (!res || res_len < 4) {
+ LOGVSUBP(LOGL_NOTICE, vsub, "AUTH RES missing or too short "
+ "(%u)\n", res_len);
+ goto out_false;
+ }
+
+ if (is_r99 && vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
+ /* We have a R99 capable UE and have a UMTS AKA capable USIM.
+ * However, the ME may still chose to only perform GSM AKA, as
+ * long as the bearer is GERAN */
+ if (is_utran && res_len != vec->res_len) {
+ LOGVSUBP(LOGL_NOTICE, vsub, "AUTH via UTRAN but "
+ "res_len(%u) != vec->res_len(%u)\n",
+ res_len, vec->res_len);
+ goto out_false;
+ }
+ }
+
+ if (res_len == vec->res_len && !memcmp(res, vec->res, res_len)) {
+ /* We have established a UMTS Security Context */
+ LOGVSUBP(LOGL_INFO, vsub, "AUTH established UMTS security "
+ "context\n");
+ vsub->sec_ctx = VLR_SEC_CTX_UMTS;
+ return true;
+ } else if (res_len == 4 && !memcmp(res, vec->sres, 4)) {
+ /* We have establieshed a GSM Security Context */
+ 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_subscriber *vsub = afp->vsub;
+
+ vlr_sub_tx_auth_fail_rep(vsub);
+}
+
+/* back-end function transmitting authentication. Caller ensures we have valid
+ * tuple */
+static int _vlr_sub_authenticate(struct vlr_subscriber *vsub)
+{
+ struct gsm_auth_tuple *at;
+ unsigned int last_keyseq = GSM_KEY_SEQ_INVAL;
+
+ if (vsub->last_tuple)
+ last_keyseq = vsub->last_tuple->key_seq;
+
+ /* Caller ensures we have vectors available */
+ at = vlr_sub_get_auth_tuple(vsub, last_keyseq);
+ OSMO_ASSERT(at && at->vec.res && at->vec.res_len >=4);
+
+ /* Transmit auth req to subscriber */
+ vsub->last_tuple = at;
+ vsub->vlr->ops.tx_auth_req(vsub->msc_conn_ref, at);
+
+ return 0;
+}
+
+/* Terminate the Auth FSM Instance and notify parent */
+static void auth_fsm_term(struct osmo_fsm_inst *fi, enum vlr_auth_fsm_result res)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscriber *vsub = afp->vsub;
+
+ /* Do one final state transition (mostly for logging purpose) */
+ if (res == VLR_AUTH_RES_PASSED)
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTHENTICATED, 0, 0);
+ else
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0);
+
+ /* return the result to the parent FSM */
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &res);
+ vsub->auth_fsm = NULL;
+}
+
+/***********************************************************************
+ * 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_subscriber *vsub = afp->vsub;
+ unsigned int last_keyseq = GSM_KEY_SEQ_INVAL;
+ struct gsm_auth_tuple *at;
+
+ OSMO_ASSERT(event == VLR_AUTH_E_START);
+
+ if (vsub->last_tuple)
+ last_keyseq = vsub->last_tuple->key_seq;
+
+ /* Check if we have vectors available */
+ at = vlr_sub_get_auth_tuple(vsub, last_keyseq);
+ if (!at) {
+ /* Obtain_Authentication_Sets_VLR */
+ vlr_sub_req_sai(vsub, NULL, NULL);
+ 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_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP,
+ vlr_timer(vsub->vlr, 3260), 3260);
+ _vlr_sub_authenticate(vsub);
+ }
+}
+
+/* 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_subscriber *vsub = afp->vsub;
+ struct osmo_gsup_message *gsup = data;
+ bool auth_sets_available_in_vlr;
+
+ if (vlr_sub_get_auth_tuple(vsub, 0))
+ auth_sets_available_in_vlr = true;
+
+ /* 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 (auth_sets_available_in_vlr) {
+ if (vsub->vlr->cfg.auth_reuse_old_sets) {
+ goto pass;
+ } else {
+ /* result = procedure error */
+ auth_fsm_term(fi, VLR_AUTH_RES_PROC_ERR);
+ }
+ }
+ }
+ switch (event) {
+ case VLR_AUTH_E_HLR_SAI_ACK:
+ vlr_sub_update_tuples(vsub, gsup);
+ goto pass;
+ break;
+ case VLR_AUTH_E_HLR_SAI_NACK:
+ /* lesult = unknown subscriber */
+ auth_fsm_term(fi, VLR_AUTH_RES_UNKNOWN_SUBSCR);
+ break;
+ }
+
+ return;
+pass:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP,
+ vlr_timer(vsub->vlr, 3260), 3260);
+ _vlr_sub_authenticate(vsub);
+}
+
+/* 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_subscriber *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_fsm_inst_state_chg(fi,
+ VLR_SUB_AS_WAIT_ID_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ } else {
+ auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+ }
+ } else {
+ auth_fsm_term(fi, VLR_AUTH_RES_PASSED);
+ }
+ break;
+ case VLR_AUTH_E_MS_AUTH_FAIL:
+ /* First failure, start re-sync attempt */
+ vlr_sub_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);
+ 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_subscriber *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, VLR_AUTH_RES_PROC_ERR);
+ }
+ switch (event) {
+ case VLR_AUTH_E_HLR_SAI_ACK:
+ vlr_sub_update_tuples(vsub, gsup);
+ goto pass;
+ break;
+ case VLR_AUTH_E_HLR_SAI_NACK:
+ auth_fsm_term(fi, VLR_AUTH_RES_UNKNOWN_SUBSCR);
+ break;
+ }
+
+ return;
+pass:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP_RESYNC,
+ vlr_timer(vsub->vlr, 3260), 3260);
+ _vlr_sub_authenticate(vsub);
+}
+
+/* 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_subscriber *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_fsm_inst_state_chg(fi,
+ VLR_SUB_AS_WAIT_ID_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ } else {
+ /* Result = Aborted */
+ auth_fsm_term(fi, VLR_AUTH_RES_ABORTED);
+ }
+ } else {
+ /* Result = Pass */
+ auth_fsm_term(fi, VLR_AUTH_RES_PASSED);
+ }
+ break;
+ case VLR_AUTH_E_MS_AUTH_FAIL:
+ /* Second failure: Result = Fail */
+ auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+ 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_subscriber *vsub = afp->vsub;
+ const char *mi_string = data;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_ID_IMSI:
+ if (vsub->imsi[0]&& strcmp(vsub->imsi, 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] = {
+ .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),
+ .name = "NEEDS-AUTH",
+ .action = auth_fsm_needs_auth,
+ },
+ [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),
+ .name = "NEEDS-AUTH(WAIT-AI-HLR)",
+ .action = auth_fsm_wait_ai,
+ },
+ [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),
+ .name = "WAIT-AUTH-RESP",
+ .action = auth_fsm_wait_auth_resp,
+ },
+ [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),
+ .name = "NEEDS-AUTH(WAIT-AI-HKR-RESYNC)",
+ .action = auth_fsm_wait_ai_resync,
+ },
+ [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),
+ .name = "NEEDS-AUTH(WAIT-AUTH-RESP-RESYNC)",
+ .action = auth_fsm_wait_auth_resp_resync,
+ },
+ [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),
+ .name = "WAIT-IMSI",
+ .action = auth_fsm_wait_imsi,
+ },
+ [VLR_SUB_AS_AUTHENTICATED] = {
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .name = "AUTHENTICATED",
+ },
+ [VLR_SUB_AS_AUTH_FAILED] = {
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .name = "AUTH-FAILED",
+ .onenter = auth_fsm_onenter_failed,
+ },
+};
+
+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 = DVLR,
+ .event_names = fsm_auth_event_names,
+};
+
+/***********************************************************************
+ * 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_subscriber *vsub, uint32_t log_level,
+ struct osmo_fsm_inst *parent, uint32_t parent_term_event)
+{
+ struct osmo_fsm_inst *fi;
+ struct auth_fsm_priv *afp;
+
+ fi = osmo_fsm_inst_alloc_child(&vlr_auth_fsm, parent,
+ parent_term_event);
+
+
+ afp = talloc_zero(fi, struct auth_fsm_priv);
+ if (!afp) {
+ osmo_fsm_inst_dispatch(parent, parent_term_event, 0);
+ return NULL;
+ }
+
+ afp->vsub = vsub;
+ if (vsub->imsi[0])
+ afp->by_imsi = true;
+ fi->priv = afp;
+ vsub->auth_fsm = fi;
+
+ osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
+
+ return fi;
+}
diff --git a/openbsc/src/libvlr/vlr_auth_fsm.h b/openbsc/src/libvlr/vlr_auth_fsm.h
new file mode 100644
index 000000000..c6bd013fd
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_auth_fsm.h
@@ -0,0 +1,40 @@
+#pragma once
+
+/* 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;
+};
+
+/* Result communicated back to parent FMS */
+enum vlr_auth_fsm_result {
+ VLR_AUTH_RES_ABORTED,
+ VLR_AUTH_RES_UNKNOWN_SUBSCR,
+ VLR_AUTH_RES_PROC_ERR,
+ VLR_AUTH_RES_AUTH_FAILED,
+ VLR_AUTH_RES_PASSED,
+};
+
+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 vlr_auth_fsm;
+
+struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscriber *vsub, uint32_t log_level,
+ struct osmo_fsm_inst *parent, uint32_t parent_term_event);
diff --git a/openbsc/src/libvlr/vlr_core.h b/openbsc/src/libvlr/vlr_core.h
new file mode 100644
index 000000000..69f25a001
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_core.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <openbsc/vlr.h>
+
+#define LOGGSUPP(level, gsup, fmt, args...) \
+ LOGP(DVLR, level, "GSUP(%s) " fmt, \
+ (gsup)->imsi, \
+ ## args)
+
+#define LOGVSUBP(level, vsub, fmt, args...) \
+ LOGP(DVLR, level, "SUBSCR(%s) " fmt, \
+ vlr_sub_name(vsub), ## args)
+
+
+const char *vlr_sub_name(struct vlr_subscriber *vsub);
+struct vlr_subscriber *vlr_subscr_find_by_imsi(struct vlr_instance *vlr,
+ const char *imsi);
+struct vlr_subscriber *vlr_subscr_find_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi);
+int vlr_sub_req_lu(struct vlr_subscriber *vsub, bool is_ps);
+int vlr_sub_req_sai(struct vlr_subscriber *vsub, const uint8_t *auts,
+ const uint8_t *auts_rand);
+struct vlr_subscriber *vlr_sub_alloc(struct vlr_instance *vlr);
+void vlr_sub_update_tuples(struct vlr_subscriber *vsub,
+ const struct osmo_gsup_message *gsup);
diff --git a/openbsc/src/libvlr/vlr_lu_fsm.c b/openbsc/src/libvlr/vlr_lu_fsm.c
new file mode 100644
index 000000000..3f8a65a0b
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_lu_fsm.c
@@ -0,0 +1,1088 @@
+/* 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/gsm/gsup.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+
+#define S(x) (1 << (x))
+
+#define LU_TIMEOUT_LONG 30
+
+/***********************************************************************
+ * 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[] = {
+ { UPD_HLR_VLR_E_START, "START" },
+ { UPD_HLR_VLR_E_INS_SUB_DATA, "INS-SUB-DATA" },
+ { UPD_HLR_VLR_E_ACT_TRACE_MODE, "ACT-TRACE-MODE" },
+ { UPD_HLR_VLR_E_FW_CHECK_SS_IND, "FW-CHECK-SS-IND" },
+ { UPD_HLR_VLR_E_UPD_LOC_ACK, "UPD-LOC-ACK" },
+ { UPD_HLR_VLR_E_UPD_LOC_NACK, "UPD-LOC-NACK" },
+ { 0, NULL }
+};
+
+static void upd_hlr_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscriber *vsub = fi->priv;
+
+ OSMO_ASSERT(event == UPD_HLR_VLR_E_START);
+
+ /* Send UpdateLocation to HLR */
+ vlr_sub_req_lu(vsub, vsub->vlr->cfg.is_ps);
+ 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_subscriber *vsub = fi->priv;
+
+ 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 = "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 = "WAIT_FOR_DATA",
+ .action = upd_hlr_vlr_fsm_wait_data,
+ },
+ [UPD_HLR_VLR_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+static struct osmo_fsm upd_hlr_vlr_fsm = {
+ .name = "Update_HLR_VLR",
+ .states = upd_hlr_vlr_states,
+ .num_states = ARRAY_SIZE(upd_hlr_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = upd_hlr_vlr_event_names,
+};
+
+struct osmo_fsm_inst *
+upd_hlr_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscriber *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[] = {
+ { SUB_PRES_VLR_E_START, "START" },
+ { SUB_PRES_VLR_E_READY_SM_CNF, "READY_FOR_SM-CNF" },
+ { SUB_PRES_VLR_E_READY_SM_ERR, "READY_FOR_SM-ERR" },
+ { 0, NULL }
+};
+
+static void sub_pres_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscriber *vsub = fi->priv;
+ 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_subscriber *vsub = fi->priv;
+
+ 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 = "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 = "WAIT_FOR_HLR",
+ .action = sub_pres_vlr_fsm_wait_hlr,
+ },
+ [SUB_PRES_VLR_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+static struct osmo_fsm sub_pres_vlr_fsm = {
+ .name = "Subscriber_Present_VLR",
+ .states = sub_pres_vlr_states,
+ .num_states = ARRAY_SIZE(sub_pres_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = sub_pres_vlr_event_names,
+};
+
+struct osmo_fsm_inst *sub_pres_vlr_fsm_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscriber *vsub,
+ uint32_t term_event)
+{
+ struct osmo_fsm_inst *fi;
+
+ fi = osmo_fsm_inst_alloc_child(&sub_pres_vlr_fsm, parent,
+ term_event);
+ if (!fi)
+ return NULL;
+
+ fi->priv = vsub;
+ osmo_fsm_inst_dispatch(fi, SUB_PRES_VLR_E_START, NULL);
+
+ return fi;
+}
+
+/***********************************************************************
+ * 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[] = {
+ { LU_COMPL_VLR_E_START, "START" },
+ { LU_COMPL_VLR_E_SUB_PRES_COMPL, "SUBSCR-PRES-COMPL" },
+ { LU_COMPL_VLR_E_IMEI_CHECK_ACK, "IMEI-CHECK-ACK" },
+ { LU_COMPL_VLR_E_IMEI_CHECK_NACK, "IMEI-CHECK-NACK" },
+ { LU_COMPL_VLR_E_NEW_TMSI_ACK, "NEW-TMSI-ACK" },
+ { 0, NULL }
+};
+
+struct lu_compl_vlr_priv {
+ struct vlr_subscriber *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *sub_pres_vlr_fsm;
+};
+
+static void lu_compl_vlr_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscriber *vsub = lcvp->vsub;
+ struct vlr_instance *vlr;
+ OSMO_ASSERT(vsub);
+ vlr = vsub->vlr;
+ OSMO_ASSERT(vlr);
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_START);
+
+ /* 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);
+
+ lcvp->sub_pres_vlr_fsm = sub_pres_vlr_fsm_start(fi, vsub,
+ LU_COMPL_VLR_E_SUB_PRES_COMPL);
+
+}
+
+/* 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 = fi->priv;
+ struct vlr_subscriber *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 */
+
+ /* Do we need to allocate a TMSI? */
+ if (vlr->cfg.alloc_tmsi) {
+ /* actually allocate a new TMSI */
+ vlr_sub_alloc_tmsi(vsub);
+ /* Set Ciphering Mode */
+ vlr->ops.set_ciph_mode(lcvp->msc_conn_ref);
+ if (vlr->cfg.check_imei_rqd) {
+ /* Check IMEI VLR */
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_IMEI_TMSI,
+ vlr_timer(vlr, 3270), 3270);
+ vlr->ops.tx_id_req(lcvp->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ } else {
+ /* FIXME: New TMSI.ind to MSC (*/
+ /* WAIT_FOR_TMSI_Cnf */
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_TMSI_CNF,
+ vlr_timer(vlr, 3250), 3250);
+ /* Update Location Area Ack */
+ vlr->ops.tx_lu_ack(lcvp->msc_conn_ref);
+ }
+ } else {
+ if (vlr->cfg.check_imei_rqd) {
+ /* Check IMEI VLR */
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_IMEI,
+ vlr_timer(vlr, 3270), 3270);
+ vlr->ops.tx_id_req(lcvp->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ } else {
+ /* Update Location Area Ack */
+ vsub->vlr->ops.tx_lu_ack(lcvp->msc_conn_ref);
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ }
+ }
+}
+
+/* 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 = fi->priv;
+ struct vlr_subscriber *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+ const char *imei = data;
+
+ switch (event) {
+ case LU_COMPL_VLR_E_IMEI_CHECK_ACK:
+ if (imei) {
+ /* Pass */
+ if (fi->state == LU_COMPL_VLR_S_WAIT_IMEI_TMSI) {
+ /* TMSI is to be allocated */
+ vlr_sub_alloc_tmsi(vsub);
+ vlr->ops.tx_lu_ack(lcvp->msc_conn_ref);
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_TMSI_CNF,
+ vlr_timer(vlr, 3250), 3250);
+ } else {
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ }
+ return;
+ } else {
+ /* Abort: Do nothing */
+ }
+ break;
+ case LU_COMPL_VLR_E_IMEI_CHECK_NACK:
+ /* Fail */
+ /* FIXME: IMEI Check Fail to VLR Application (Detach IMSI VLR) */
+ /* LU REJECT ILLEGAL ME */
+ vsub->vlr->ops.tx_lu_rej(vsub, GMM_CAUSE_ILLEGAL_ME);
+ break;
+ }
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+/* Waiting for TMSI confirmation */
+static void lu_compl_vlr_wait_tmsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_NEW_TMSI_ACK);
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+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) |
+ S(LU_COMPL_VLR_S_WAIT_IMEI),
+ .name = "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_TMSI_CNF) |
+ S(LU_COMPL_VLR_S_DONE),
+ .name = "WAIT-SUBSCR-PRES-COMPL",
+ .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 = "WAIT-CHECK-IMEI",
+ .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 = "WAIT-CHECK-IMEI(TMSI)",
+ .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 = "WAIT-TMSI-CONF",
+ .action = lu_compl_vlr_wait_tmsi,
+ },
+ [LU_COMPL_VLR_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+static struct osmo_fsm lu_compl_vlr_fsm = {
+ .name = "Location_Update_Completion_VLR",
+ .states = lu_compl_vlr_states,
+ .num_states = ARRAY_SIZE(lu_compl_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = lu_compl_vlr_event_names,
+};
+
+struct osmo_fsm_inst *
+lu_compl_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscriber *vsub,
+ void *msc_conn_ref,
+ uint32_t term_event)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu_compl_vlr_priv *lcvp;
+
+ fi = osmo_fsm_inst_alloc_child(&lu_compl_vlr_fsm, parent,
+ term_event);
+ if (!fi)
+ return NULL;
+
+ lcvp = talloc_zero(fi, struct lu_compl_vlr_priv);
+ lcvp->vsub = vsub;
+ lcvp->msc_conn_ref = msc_conn_ref;
+ fi->priv = lcvp;
+
+ osmo_fsm_inst_dispatch(fi, LU_COMPL_VLR_E_START, NULL);
+
+ return fi;
+}
+
+
+/***********************************************************************
+ * Update_Location_Area_VLR, TS 23.012 Chapter 4.1.2.1
+ ***********************************************************************/
+
+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_IMSI, /* Waiting for IMSI from MS */
+ VLR_ULA_S_WAIT_HLR_UPD,/* Waiting for end of HLR update */
+ VLR_ULA_S_WAIT_LU_COMPL, /* Waitig for LU complete */
+ VLR_ULA_S_WAIT_LU_COMPL_STANDALONE, /* Standalone VLR */
+ VLR_ULA_S_DONE
+};
+
+static const struct value_string fsm_lu_event_names[] = {
+ { VLR_ULA_E_UPDATE_LA, "UPDATE-LOCATION" },
+ { VLR_ULA_E_SEND_ID_ACK, "PVLR-SEND-ID-ACK" },
+ { VLR_ULA_E_SEND_ID_NACK, "PVLR-SEND-ID-NACK" },
+ { VLR_ULA_E_AUTH_RES, "AUTH-RES" },
+ { VLR_ULA_E_ID_IMSI, "MS-ID-IMSI" },
+ { VLR_ULA_E_ID_IMEI, "MS-ID-IMEI" },
+ { VLR_ULA_E_ID_IMEISV, "MS-ID-IMEISV" },
+ { VLR_ULA_E_HLR_LU_RES, "HLR-LU-RES" },
+ { VLR_ULA_E_UPD_HLR_COMPL, "UPD-HLR-VLR-COMPL-RES" },
+ { VLR_ULA_E_LU_COMPL_TERM, "LU-COMPL-VLR-RES" },
+ { VLR_ULA_E_NEW_TMSI_ACK, "NEW-TMSI-ACK" },
+ { 0, NULL }
+};
+
+struct lu_fsm_priv {
+ struct vlr_instance *vlr;
+ struct vlr_subscriber *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *upd_hlr_vlr_fsm;
+ struct osmo_fsm_inst *lu_compl_vlr_fsm;
+
+ enum vlr_lu_type 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;
+};
+
+
+/* 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 configued list of LAIs */
+ return true;
+}
+
+/* Determine if authentication is required */
+static bool is_auth_required(struct vlr_subscriber *vsub)
+{
+ /* The cases where the authentication procedure should be used
+ * are defined in 3GPP TS 33.102 */
+ /* We always require authentication, for now */
+ return true;
+}
+
+/* Determine if a HLR Update is required */
+static bool hlr_update_needed(struct vlr_subscriber *vsub)
+{
+ /* TODO: properly decide this, rather than always assuming we
+ * need to update the HLR. */
+ return true;
+}
+
+/* Terminate a Location Update FSM Instance */
+static void lu_fsm_term(struct osmo_fsm_inst *fi)
+{
+ /* if the MSC is registered as parent, it will get notified via
+ * the usual signalling */
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+/* 4.1.2.1 Node 4 */
+static void vlr_loc_upd_node_4(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscriber *vsub = lfp->vsub;
+ struct vlr_instance *vlr = lfp->vlr;
+ bool hlr_unknown = false;
+
+ if (hlr_unknown) {
+ /* FIXME: Delete subscriber record */
+ /* LU REJ: Roaming not allowed */
+ vlr->ops.tx_lu_rej(lfp->msc_conn_ref, GSM48_REJECT_ROAMING_NOT_ALLOWED);
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+ lu_fsm_term(fi);
+ } else {
+ /* 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);
+ }
+}
+
+/* 4.1.2.1 Node B */
+static void vlr_loc_upd_node_b(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ 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);
+ lfp->lu_compl_vlr_fsm =
+ lu_compl_vlr_proc_start(fi, lfp->vsub,
+ lfp->msc_conn_ref,
+ VLR_ULA_E_LU_COMPL_TERM);
+ }
+}
+
+/* 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 = fi->priv;
+ struct vlr_subscriber *vsub = lfp->vsub;
+ OSMO_ASSERT(vsub);
+
+ 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);
+ }
+}
+
+static void vlr_loc_upd_node1(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscriber *vsub = lfp->vsub;
+
+ OSMO_ASSERT(vsub);
+
+ if (is_auth_required(lfp->vsub)) {
+ /* 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->log_level,
+ fi, VLR_ULA_E_AUTH_RES);
+ } else {
+ /* no need for authentication */
+ vlr_loc_upd_post_auth(fi);
+ }
+}
+
+static void vlr_loc_upd_node2(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ OSMO_ASSERT(lfp->vsub);
+
+ /* Obtain_IMSI_VLR */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ vlr->ops.tx_id_req(lfp->msc_conn_ref, GSM_MI_TYPE_IMSI);
+ /* will continue at vlr_loc_upd_node1() once IMSI arrives */
+}
+
+static void assoc_lfp_with_sub(struct osmo_fsm_inst *fi, struct vlr_subscriber *vsub)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ OSMO_ASSERT(vsub->lu_fsm == NULL);
+ vsub->lu_fsm = fi;
+ vsub->msc_conn_ref = lfp->msc_conn_ref;
+ lfp->vsub = vsub;
+ /* Tell MSC to associate this subscriber with the given
+ * connection */
+ vlr->ops.subscr_assoc(lfp->msc_conn_ref, lfp->vsub);
+}
+
+/* 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 = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+ struct vlr_subscriber *vsub = NULL;
+
+ /* TODO: PUESBINE related handling */
+ if (!lfp->imsi[0]) {
+ /* TMSI was used */
+ lfp->lu_by_tmsi = true;
+ /* Is previous LAI in this VLR? */
+ if (!lai_in_this_vlr(vlr, &lfp->old_lai)) {
+ vsub = vlr_sub_alloc(vlr);
+ OSMO_ASSERT(vsub);
+ vsub->tmsi = lfp->tmsi; /* FIXME: what if clash? */
+ vsub->sub_dataconf_by_hlr_ind = false;
+ assoc_lfp_with_sub(fi, vsub);
+#if 0
+ /* FIXME: check previous VLR, (3) */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_PVLR,
+ LU_TIMEOUT_LONG, 0);
+#endif
+ vlr_loc_upd_node2(fi);
+ } else {
+ /* Is TMSI known */
+ vsub = vlr_subscr_find_by_tmsi(vlr, lfp->tmsi);
+ if (!vsub) {
+ vsub = vlr_sub_alloc(vlr);
+ OSMO_ASSERT(vsub);
+ vsub->sub_dataconf_by_hlr_ind = false;
+ vsub->tmsi = lfp->tmsi; /* FIXME: what if clash? */
+ assoc_lfp_with_sub(fi, vsub);
+ vlr_loc_upd_node2(fi);
+ } else {
+ assoc_lfp_with_sub(fi, vsub);
+ /* We cannot have MSC area change, as the VLR
+ * serves only one MSC */
+ vlr_loc_upd_node1(fi);
+ }
+ }
+ } else {
+ /* IMSI was used */
+ /* Is subscriber known in VLR? */
+ vsub = vlr_subscr_find_by_imsi(vlr, lfp->imsi);
+ if (!vsub) {
+ vsub = vlr_sub_alloc(vlr);
+ OSMO_ASSERT(vsub);
+ strncpy(vsub->imsi, lfp->imsi, sizeof(vsub->imsi));
+ vsub->imsi[sizeof(vsub->imsi)-1] = '\0';
+ }
+ vsub->sub_dataconf_by_hlr_ind = false;
+ assoc_lfp_with_sub(fi, vsub);
+ vlr_loc_upd_node1(fi);
+ }
+}
+
+
+static void lu_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ OSMO_ASSERT(event == VLR_ULA_E_UPDATE_LA);
+
+ if (1) { // FIXME
+ //if (lfp->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_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_IMEISV,
+ vlr_timer(vlr, 3270), 3270);
+ }
+}
+
+static void lu_fsm_wait_imeisv(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case VLR_ULA_E_ID_IMEISV:
+ /* FIXME: copy IMEISV */
+ _start_lu_main(fi);
+ 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(fi);
+ break;
+ case VLR_ULA_E_SEND_ID_NACK:
+ vlr_loc_upd_node2(fi);
+ 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 = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+ enum vlr_auth_fsm_result *res = data;
+ uint8_t rej_cause = 0;
+
+ OSMO_ASSERT(event == VLR_ULA_E_AUTH_RES);
+
+ lfp->upd_hlr_vlr_fsm = NULL;
+
+ if (res) {
+ switch (*res) {
+ case VLR_AUTH_RES_PASSED:
+ /* Result == Pass */
+ vlr_loc_upd_post_auth(fi);
+ return;
+ break;
+ case VLR_AUTH_RES_ABORTED:
+ /* go to Idle with no response */
+ rej_cause = 0;
+ break;
+ case VLR_AUTH_RES_UNKNOWN_SUBSCR:
+ /* FIXME: delete subscribe record */
+ rej_cause = GSM48_REJECT_IMSI_UNKNOWN_IN_HLR;
+ break;
+ case VLR_AUTH_RES_AUTH_FAILED:
+ /* cause = illegal subscriber */
+ rej_cause = GSM48_REJECT_ILLEGAL_MS;
+ break;
+ case VLR_AUTH_RES_PROC_ERR:
+ /* cause = system failure */
+ rej_cause = GSM48_REJECT_NETWORK_FAILURE;
+ break;
+ }
+ } else
+ rej_cause = GSM48_REJECT_NETWORK_FAILURE;
+
+ if (rej_cause)
+ vlr->ops.tx_lu_rej(lfp->msc_conn_ref, rej_cause);
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+ lu_fsm_term(fi);
+}
+
+static void lu_fsm_wait_imsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscriber *vsub = lfp->vsub;
+ char *mi_string = data;
+
+ switch (event) {
+ case VLR_ULA_E_ID_IMSI:
+ strncpy(vsub->imsi, mi_string, sizeof(vsub->imsi));
+ vsub->imsi[sizeof(vsub->imsi)-1] = '\0';
+ vlr_loc_upd_node1(fi);
+ 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 = fi->priv;
+ struct vlr_subscriber *vsub = lfp->vsub;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ 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);
+ lfp->lu_compl_vlr_fsm =
+ lu_compl_vlr_proc_start(fi, vsub, lfp->msc_conn_ref,
+ VLR_ULA_E_LU_COMPL_TERM);
+ /* continue in MSC ?!? */
+ } else {
+ /* unsuccessful case */
+ enum gsm48_gmm_cause cause =
+ *(enum gsm48_gmm_cause *)data;
+ 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);
+ lfp->lu_compl_vlr_fsm =
+ lu_compl_vlr_proc_start(fi, vsub,
+ lfp->msc_conn_ref,
+ VLR_ULA_E_LU_COMPL_TERM);
+ } else {
+ vlr->ops.tx_lu_rej(vsub, cause);
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+ lu_fsm_term(fi);
+ /* continue in MSC ?!? */
+ }
+ }
+ 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 = fi->priv;
+
+ 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_TERM:
+ lfp->lu_compl_vlr_fsm = NULL;
+
+ if (1 /* pass */) {
+ /* Update Register */
+ /* TODO: Set_Notification_Type 23.078 */
+ /* TODO: Notify_gsmSCF 23.078 */
+ /* TODO: Authenticated Radio Contact Established -> ARC */
+ }
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+ lu_fsm_term(fi);
+ 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 = fi->priv;
+ struct vlr_subscriber *vsub = lfp->vsub;
+
+ 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_TERM:
+ vsub->sub_dataconf_by_hlr_ind = false;
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+ lu_fsm_term(fi);
+ break;
+ }
+}
+
+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_DONE),
+ .name = "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_DONE),
+ .name = "WAIT-ID-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_DONE),
+ .name = "WAIT-PVLR-RESP",
+ .action = lu_fsm_wait_pvlr,
+ },
+ [VLR_ULA_S_WAIT_AUTH] = {
+ .in_event_mask = S(VLR_ULA_E_AUTH_RES),
+ .out_state_mask = S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = "WAIT-AUTH-COMPL",
+ .action = lu_fsm_wait_auth,
+ },
+ [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_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = "WAIT-ID-IMSI",
+ .action = lu_fsm_wait_imsi,
+ },
+ [VLR_ULA_S_WAIT_HLR_UPD] = {
+ .in_event_mask = S(VLR_ULA_E_HLR_LU_RES) |
+ S(VLR_ULA_E_UPD_HLR_COMPL),
+ .out_state_mask = S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_LU_COMPL_STANDALONE) |
+ S(VLR_ULA_S_DONE),
+ .name = "WAIT-HLR-LU-COMPL",
+ .action = lu_fsm_wait_hlr_ul_res,
+ },
+ [VLR_ULA_S_WAIT_LU_COMPL] = {
+ .in_event_mask = S(VLR_ULA_E_LU_COMPL_TERM) |
+ S(VLR_ULA_E_NEW_TMSI_ACK),
+ .out_state_mask = S(VLR_ULA_S_DONE),
+ .name = "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_TERM) |
+ S(VLR_ULA_E_NEW_TMSI_ACK),
+ .out_state_mask = S(VLR_ULA_S_DONE),
+ .name = "WAIT-LU-COMPL(STANDALONE)",
+ .action = lu_fsm_wait_lu_compl_standalone,
+ },
+ [VLR_ULA_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+static struct osmo_fsm vlr_lu_fsm = {
+ .name = "Update_Location_Area_VLR",
+ .states = vlr_lu_fsm_states,
+ .num_states = ARRAY_SIZE(vlr_lu_fsm_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = fsm_lu_event_names,
+};
+
+struct vlr_subscriber *
+vlr_loc_update(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)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu_fsm_priv *lfp;
+ char buf[16];
+ const char *id_str;
+
+ if (imsi)
+ id_str = imsi;
+ else {
+ snprintf(buf, sizeof(buf), "%08x", tmsi);
+ id_str = buf;
+ }
+
+ fi = osmo_fsm_inst_alloc(&vlr_lu_fsm, vlr, NULL, LOGL_DEBUG,
+ id_str);
+ 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->type = type;
+ lfp->old_lai = *old_lai;
+ lfp->new_lai = *new_lai;
+ lfp->lu_by_tmsi = true;
+ 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;
+
+ osmo_fsm_inst_dispatch(fi, VLR_ULA_E_UPDATE_LA, NULL);
+
+ /* ugly: we assume that dispatching the start event has created
+ * the subscriber */
+ return lfp->vsub;
+}
+
+void vlr_lu_fsm_init(void)
+{
+ osmo_fsm_register(&vlr_lu_fsm);
+ osmo_fsm_register(&upd_hlr_vlr_fsm);
+ osmo_fsm_register(&sub_pres_vlr_fsm);
+ osmo_fsm_register(&lu_compl_vlr_fsm);
+}
diff --git a/openbsc/src/libvlr/vlr_lu_fsm.h b/openbsc/src/libvlr/vlr_lu_fsm.h
new file mode 100644
index 000000000..9cce54707
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_lu_fsm.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+
+void vlr_lu_fsm_init(void);
diff --git a/openbsc/src/osmo-nitb/Makefile.am b/openbsc/src/osmo-nitb/Makefile.am
index 3b7cc8d50..cf762df69 100644
--- a/openbsc/src/osmo-nitb/Makefile.am
+++ b/openbsc/src/osmo-nitb/Makefile.am
@@ -11,6 +11,7 @@ osmo_nitb_SOURCES = bsc_hack.c
osmo_nitb_LDADD = \
$(top_builddir)/src/libbsc/libbsc.a \
$(top_builddir)/src/libmsc/libmsc.a \
+ $(top_builddir)/src/libvlr/libvlr.a \
$(top_builddir)/src/libtrau/libtrau.a \
$(top_builddir)/src/libcommon/libcommon.a \
-ldbi $(LIBCRYPT) \
diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am
index 09298a35c..813ac0179 100644
--- a/openbsc/tests/Makefile.am
+++ b/openbsc/tests/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = gsm0408 db channel mgcp gprs abis gbproxy trau subscr mm_auth
+SUBDIRS = gsm0408 db channel mgcp gprs abis gbproxy trau subscr mm_auth vlr
if BUILD_NAT
SUBDIRS += bsc-nat bsc-nat-trie
diff --git a/openbsc/tests/vlr/Makefile.am b/openbsc/tests/vlr/Makefile.am
new file mode 100644
index 000000000..1bc437ca2
--- /dev/null
+++ b/openbsc/tests/vlr/Makefile.am
@@ -0,0 +1,16 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS) $(LIBOSMOABIS_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+EXTRA_DIST = vlr_test.ok
+
+noinst_PROGRAMS = vlr_test
+
+vlr_test_SOURCES = vlr_test.c
+vlr_test_LDADD = $(top_builddir)/src/libvlr/libvlr.a \
+ $(top_builddir)/src/gprs/gprs_gsup_client.o \
+ $(top_builddir)/src/gprs/oap.o \
+ $(top_builddir)/src/libcommon/debug.o \
+ $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) \
+ $(LIBRARY_DL) $(LIBCRYPTO_LIBS)
+
diff --git a/openbsc/tests/vlr/vlr_test.c b/openbsc/tests/vlr/vlr_test.c
new file mode 100644
index 000000000..1466e2b1b
--- /dev/null
+++ b/openbsc/tests/vlr/vlr_test.c
@@ -0,0 +1,684 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#define S(x) (1 << (x))
+
+/*
+ * TODO:
+ * * test FSM for all testvlr_mode (and more)
+ * * test also the time-outs in the vlr code
+ * * test for memory leaks
+ * * how to get the HLR running? Or test against stub?
+ * * test disappearing MS connection
+ * * test absence of HLR
+ */
+
+void *tall_bsc_ctx;
+static struct vlr_instance *g_vlr;
+
+/***********************************************************************
+ * Finite State Machine simulating MS and MSC towards VLR
+ ***********************************************************************/
+
+static void timer_error_cb(struct osmo_fsm_inst *fi)
+{
+ struct vlr_subscriber *vsub = fi->priv;
+ LOGPFSM(fi, "timer expired waiting for completion\n");
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ vlr_sub_cleanup(vsub);
+}
+
+enum testvlr_mode {
+ MODE_SUCCESS,
+ MODE_SUCCESS_TMSI,
+ MODE_AUTH_FAIL,
+ MODE_AUTH_RESYNC,
+};
+
+struct testvlr_priv {
+ enum testvlr_mode mode;
+ uint32_t tmsi;
+ char imsi[16];
+ char imei[16];
+ struct osmo_location_area_id old_lai;
+ struct osmo_location_area_id new_lai;
+
+ struct vlr_subscriber *subscr;
+};
+
+#define fsi_priv(x) (struct testvlr_priv *)(x)->priv
+
+enum f_state {
+ /*! initial state */
+ ST_NULL,
+ /*! LU was sent by MS */
+ ST_LU_SENT,
+ /*! waiting for auth re-sync */
+ ST_RESYNC_SENT,
+ /* Waiting for LU ACK */
+ ST_WAIT_LU_ACK,
+ ST_DONE,
+ ST_FAILED,
+};
+
+enum f_event {
+ /* events from MS */
+ EVT_MS_TX_LU, /* transmit LU REQ to network */
+ EVT_MS_TX_ID_RESP, /* tranmit ID RSP to network */
+ EVT_MS_TX_AUTH_RESP, /* transmit AUTH RESP to network */
+ EVT_MS_TX_AUTH_FAIL, /* transmit AUTH FAIL to network */
+ EVT_MS_CONN_LOST, /* connection to MS was lost */
+
+ /* events from VLR */
+ EVT_VLR_AUTH_REQ, /* transmit AUTH REQ to MS */
+ EVT_VLR_ID_REQ_IMSI, /* transmit ID REQ(IMSI) to MS */
+ EVT_VLR_ID_REQ_IMEI, /* tramsmit ID REQ(IMEI) to MS */
+ EVT_VLR_ID_REQ_IMEISV, /* trasnmit ID REQ(IMEISV) to MS */
+ EVT_VLR_AUTH_REJ, /* transmit AUTH REJ to MS */
+ EVT_VLR_SET_CIPH, /* transmit SET CIPH to MS */
+ EVT_VLR_LU_ACK, /* transmit LU ACK to MS */
+ EVT_VLR_LU_REJ, /* transmit LU REJ to MS */
+};
+
+static struct value_string f_event_names[] = {
+ { EVT_MS_TX_LU, "MS-TX-LU" },
+ { EVT_MS_TX_ID_RESP, "MS-TX-ID-RESP" },
+ { EVT_MS_TX_AUTH_RESP, "MS-TX-AUTH-RESP" },
+ { EVT_MS_TX_AUTH_FAIL, "MS-TX-AUTH-FAIL" },
+ { EVT_MS_CONN_LOST, "MS-CONN-LOST" },
+
+ { EVT_VLR_AUTH_REQ, "VLR-AUTH-REQ" },
+ { EVT_VLR_ID_REQ_IMSI, "VLR-ID-REQ-IMSI" },
+ { EVT_VLR_ID_REQ_IMEI, "VLR-ID-REQ-IMEI" },
+ { EVT_VLR_ID_REQ_IMEISV,"VLR-ID-REQ-IMEISV" },
+ { EVT_VLR_AUTH_REJ, "VLR-AUTH-REJ" },
+ { EVT_VLR_SET_CIPH, "VLR-SET-CIPH" },
+ { EVT_VLR_LU_ACK, "VLR-LU-ACK" },
+ { EVT_VLR_LU_REJ, "VLR-LU-REJ" },
+ { 0, NULL }
+};
+
+static void fsm_f_allstate(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+ uint8_t mi[16];
+ unsigned int mi_len;
+
+ switch (event) {
+ case EVT_VLR_ID_REQ_IMSI:
+ if (priv->mode != MODE_SUCCESS_TMSI) {
+ LOGP(DGPRS, LOGL_NOTICE, "Unexpected ID REQ "
+ "(IMSI)\n");
+ }
+ mi_len = gsm48_generate_mid_from_imsi(mi, priv->imsi);
+ vlr_sub_rx_id_resp(priv->subscr, mi+2, mi_len-2);
+ break;
+ case EVT_VLR_ID_REQ_IMEI:
+ mi_len = gsm48_generate_mid_from_imsi(mi, priv->imei);
+ mi[0] = (mi[0] & 0xf8) | GSM_MI_TYPE_IMEI;
+ vlr_sub_rx_id_resp(priv->subscr+2, mi, mi_len-2);
+ break;
+ case EVT_VLR_ID_REQ_IMEISV:
+ mi_len = gsm48_generate_mid_from_imsi(mi, priv->imei);
+ mi[0] = (mi[0] & 0xf8) | GSM_MI_TYPE_IMEISV;
+ vlr_sub_rx_id_resp(priv->subscr, mi+2, mi_len-2);
+ break;
+ case EVT_MS_CONN_LOST:
+ vlr_sub_disconnected(priv->subscr);
+ /* IDEA: not release but keep around in extra state to
+ * see if VLR still sends us anything? */
+ osmo_fsm_inst_free(fi);
+ break;
+ }
+}
+
+static void fsm_f_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+ uint32_t tmsi = 0;
+ const char *imsi = NULL;
+
+ switch (event) {
+ case EVT_MS_TX_LU:
+ /* send LU to VLR */
+ if (priv->mode == MODE_SUCCESS)
+ imsi = priv->imsi;
+ else
+ tmsi = priv->tmsi;
+ priv->subscr = vlr_loc_update(g_vlr, fi,
+ VLR_LU_TYPE_IMSI_ATTACH,
+ tmsi, imsi,
+ &priv->old_lai,
+ &priv->new_lai);
+ OSMO_ASSERT(priv->subscr);
+ osmo_fsm_inst_state_chg(fi, ST_LU_SENT, 0, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+static void fsm_f_lu_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct gsm_auth_tuple *at = NULL;
+ struct testvlr_priv *priv = fsi_priv(fi);
+ uint8_t res_fail[4];
+ uint8_t auts[16];
+
+ switch (event) {
+ case EVT_VLR_AUTH_REQ:
+ at = data;
+ OSMO_ASSERT(at);
+ DEBUGP(DGPRS, "%s: at->res=%s\n", __func__, osmo_hexdump(at->vec.res, at->vec.res_len));
+ switch (priv->mode) {
+ case MODE_SUCCESS:
+ case MODE_SUCCESS_TMSI:
+ /* return matching SRES/AUTS */
+ vlr_sub_rx_auth_resp(priv->subscr, true, false,
+ at->vec.res, at->vec.res_len);
+ break;
+ case MODE_AUTH_FAIL:
+ /* return not matching SRES/AUTS */
+ vlr_sub_rx_auth_resp(priv->subscr, true, false,
+ res_fail, sizeof(res_fail));
+ /* FIXME: state transition? */
+ break;
+ case MODE_AUTH_RESYNC:
+ /* return SRES/AUTS requesting re-sync */
+ /* FIXME: generate a proper authenticating
+ * re-sync request */
+ vlr_sub_rx_auth_fail(priv->subscr, auts);
+ /* FIXME: state transition? */
+ osmo_fsm_inst_state_chg(fi, ST_RESYNC_SENT, 0, 0);
+ break;
+ }
+ osmo_fsm_inst_state_chg(fi, ST_WAIT_LU_ACK, 0, 0);
+ break;
+ case EVT_VLR_LU_REJ:
+ {
+ uint8_t cause = *(uint8_t *)data;
+ LOGP(DGPRS, LOGL_NOTICE, "LU(%s): Rejected; cause=0x%02x\n",
+ priv->imsi, cause);
+
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void fsm_f_resync_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+ struct gsm_auth_tuple *at = NULL;
+
+ /* second auth request is supposed to succed after the
+ * re-sync procedure before */
+ switch (event) {
+ case EVT_VLR_AUTH_REQ:
+ at = data;
+ /* return matching SRES/AUTS now */
+ vlr_sub_rx_auth_resp(priv->subscr, true, false,
+ at->vec.res, at->vec.res_len);
+ osmo_fsm_inst_state_chg(fi, ST_WAIT_LU_ACK, 0, 0);
+ break;
+ }
+}
+
+static void fsm_f_wait_lu_ack(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+
+ switch (event) {
+ case EVT_VLR_LU_ACK:
+ if (priv->subscr->tmsi != GSM_RESERVED_TMSI) {
+ /* we need to send an TMSI REALLOC COMPL */
+ vlr_sub_rx_tmsi_reall_compl(priv->subscr);
+ }
+ osmo_fsm_inst_state_chg(fi, ST_DONE, 0, 0);
+ break;
+ case EVT_VLR_LU_REJ:
+ osmo_fsm_inst_state_chg(fi, ST_FAILED, 0, 0);
+ break;
+ }
+}
+
+static void fsm_f_imsi_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case EVT_MS_TX_ID_RESP:
+ break;
+ }
+}
+
+static void fsm_f_areq_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case EVT_MS_TX_AUTH_RESP:
+ break;
+ case EVT_MS_TX_AUTH_FAIL:
+ break;
+ }
+}
+
+static struct osmo_fsm_state fsm_success_states[] = {
+ [ST_NULL] = {
+ .in_event_mask = S(EVT_MS_TX_LU),
+ .out_state_mask = S(ST_LU_SENT),
+ .name = "NULL",
+ .action = fsm_f_null,
+ },
+ [ST_LU_SENT] = {
+ .in_event_mask = S(EVT_VLR_AUTH_REQ) |
+ S(EVT_VLR_LU_REJ),
+ //.out_state_mask = S(ST_IDREQ_IMSI_SENT) | S(ST_AUTH_REQ_SENT),
+ .out_state_mask = S(ST_WAIT_LU_ACK),
+ .name = "LU Sent",
+ .action = fsm_f_lu_sent,
+ },
+ [ST_RESYNC_SENT] = {
+ .in_event_mask = S(EVT_VLR_AUTH_REQ),
+ .out_state_mask = S(ST_WAIT_LU_ACK),
+ .name = "AUTH-RESYNC sent",
+ .action = fsm_f_imsi_sent,
+ },
+ [ST_WAIT_LU_ACK] = {
+ .in_event_mask = S(EVT_VLR_LU_ACK) |
+ S(EVT_VLR_SET_CIPH) |
+ S(EVT_VLR_LU_REJ),
+ .out_state_mask = S(ST_DONE),
+ .name = "WAIT-LU-ACK",
+ .action = fsm_f_wait_lu_ack,
+ },
+ [ST_DONE] = {
+ .name = "DONE"
+ },
+};
+
+static struct osmo_fsm vlr_test_fsm = {
+ .name = "VLR Test FSM",
+ .states = fsm_success_states,
+ .num_states = ARRAY_SIZE(fsm_success_states),
+ .log_subsys = DGPRS,
+ .event_names = f_event_names,
+ .allstate_event_mask = S(EVT_MS_CONN_LOST) |
+ S(EVT_VLR_ID_REQ_IMSI) |
+ S(EVT_VLR_ID_REQ_IMEI) |
+ S(EVT_VLR_ID_REQ_IMEISV),
+ .allstate_action = fsm_f_allstate,
+};
+
+/* Testing of Subscriber_Present_VLR */
+
+enum test_sub_pres_state {
+ TSPV_S_INIT,
+ TSPV_S_RUNNING,
+};
+
+enum test_sub_pres_evt {
+ TSPV_E_START,
+ TSPV_E_COMPL,
+};
+
+static void tspv_f_running(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vlr_subscriber *vsub = fi->priv;
+
+ switch (event) {
+ case TSPV_E_COMPL:
+ OSMO_ASSERT(vsub);
+ OSMO_ASSERT(vsub->ms_not_reachable_flag == false);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ }
+}
+
+static void tspv_f_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_fsm_inst *spv;
+ struct vlr_subscriber *vsub = fi->priv;
+
+ switch (event) {
+ case TSPV_E_START:
+ OSMO_ASSERT(vsub);
+ vsub->ms_not_reachable_flag = true;
+ spv = sub_pres_vlr_fsm_start(fi, vsub, TSPV_E_COMPL);
+ OSMO_ASSERT(spv);
+ osmo_fsm_inst_state_chg(fi, TSPV_S_RUNNING, 4, 0);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state test_sub_pres_vlr_states[] = {
+ [TSPV_S_INIT] = {
+ .in_event_mask = S(TSPV_E_START),
+ .out_state_mask = S(TSPV_S_RUNNING),
+ .name = "INIT",
+ .action = tspv_f_init,
+ },
+ [TSPV_S_RUNNING] = {
+ .in_event_mask = S(TSPV_E_COMPL),
+ .out_state_mask = 0,
+ .name = "RUNNING",
+ .action = tspv_f_running,
+ },
+};
+
+static struct osmo_fsm test_sub_pres_vlr_fsm = {
+ .name = "Test Subscriber_Present_VLR",
+ .states = test_sub_pres_vlr_states,
+ .num_states = ARRAY_SIZE(test_sub_pres_vlr_states),
+ .log_subsys = DGPRS,
+ .event_names = f_event_names,
+ .timer_cb = timer_error_cb,
+};
+
+static void start_sub_pres_vlr(void *ctx, uint32_t tmsi, const char *imsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct vlr_subscriber *vsub = vlr_sub_alloc(g_vlr);
+
+ vsub->tmsi = tmsi;
+ strncpy(vsub->imsi, imsi, sizeof(vsub->imsi));
+ fi = osmo_fsm_inst_alloc(&test_sub_pres_vlr_fsm, ctx, vsub, LOGL_DEBUG, vsub->imsi);
+ osmo_fsm_inst_dispatch(fi, TSPV_E_START, NULL);
+}
+
+/* Testing of Update_HLR_VLR */
+
+enum test_update_hlr_vlr_state {
+ TUHV_S_INIT,
+ TUHV_S_RUNNING,
+};
+
+enum test_update_hlr_vlr_event {
+ TUHV_E_START,
+ TUHV_E_COMPL,
+};
+
+static void tuhv_f_running(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vlr_subscriber *vsub = fi->priv;
+ enum gsm48_gmm_cause *res = data;
+
+ switch (event) {
+ case TUHV_E_COMPL:
+ if (!res) {
+ /* Success */
+ LOGPFSM(fi, "success\n");
+ } else {
+ /* error */
+ LOGPFSM(fi, "errror cause=0x%u\n", *res);
+ }
+ vlr_sub_cleanup(vsub);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ }
+}
+
+static void tuhv_f_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_fsm_inst *child;
+ struct vlr_subscriber *vsub = fi->priv;
+
+ switch (event) {
+ case TUHV_E_START:
+ child = upd_hlr_vlr_proc_start(fi, vsub, TUHV_E_COMPL);
+ OSMO_ASSERT(child);
+ osmo_fsm_inst_state_chg(fi, TUHV_S_RUNNING, 4, 0);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state test_upd_hlr_vlr_states[] = {
+ [TUHV_S_INIT] = {
+ .in_event_mask = S(TUHV_E_START),
+ .out_state_mask = S(TUHV_S_RUNNING),
+ .name = "INIT",
+ .action = tuhv_f_init,
+ },
+ [TUHV_S_RUNNING] = {
+ .in_event_mask = S(TUHV_E_COMPL),
+ .out_state_mask = 0,
+ .name = "RUNNING",
+ .action = tuhv_f_running,
+ },
+};
+
+static struct osmo_fsm test_upd_hlr_vlr_fsm = {
+ .name = "Test Update_HLR_VLR",
+ .states = test_upd_hlr_vlr_states,
+ .num_states = ARRAY_SIZE(test_upd_hlr_vlr_states),
+ .log_subsys = DGPRS,
+ .event_names = f_event_names,
+ .timer_cb = timer_error_cb,
+};
+
+static void start_upd_hlr_vlr(void *ctx, uint32_t tmsi, const char *imsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct vlr_subscriber *vsub = vlr_sub_alloc(g_vlr);
+
+ vsub->tmsi = tmsi;
+ strncpy(vsub->imsi, imsi, sizeof(vsub->imsi));
+
+
+ fi = osmo_fsm_inst_alloc(&test_upd_hlr_vlr_fsm, ctx, vsub, LOGL_DEBUG,
+ vsub->imsi);
+ /* we need to set this to fool vlr.c in an ongoing LU */
+ vsub->lu_fsm = fi;
+ osmo_fsm_inst_dispatch(fi, TUHV_E_START, NULL);
+}
+
+/***********************************************************************
+ * Integration with VLR code
+ ***********************************************************************/
+
+static struct vlr_instance *g_vlr;
+
+/* VLR asks us to send an authentication request */
+static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct gsm_auth_tuple *at)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ OSMO_ASSERT(at);
+ DEBUGP(DGPRS, "%s: RES=%s\n", __func__,
+ osmo_hexdump_nospc(at->vec.res, at->vec.res_len));
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_AUTH_REQ, at);
+ return 0;
+}
+
+/* VLR asks us to send an authentication reject */
+static int msc_vlr_tx_auth_rej(void *msc_conn_ref)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_AUTH_REJ, NULL);
+ return 0;
+}
+
+/* VLR asks us to transmit an Identity Request of given type */
+static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ uint32_t event;
+
+ DEBUGP(DGPRS, "%s (%u)\n", __func__, mi_type);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ event = EVT_VLR_ID_REQ_IMSI;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ event = EVT_VLR_ID_REQ_IMEI;
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ event = EVT_VLR_ID_REQ_IMEISV;
+ break;
+ default:
+ LOGP(DGPRS, LOGL_ERROR, "Unknown identity 0x%02x\n",
+ mi_type);
+ return -1;
+ }
+ osmo_fsm_inst_dispatch(fi, event, NULL);
+ return 0;
+}
+
+/* VLR asks us to transmit a Location Update Accept */
+static int msc_vlr_tx_lu_ack(void *msc_conn_ref)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_LU_ACK, NULL);
+ return 0;
+}
+
+/* VLR asks us to transmit a Location Update Reject */
+static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_LU_REJ, (void *) &cause);
+ return 0;
+}
+
+static int msc_vlr_set_ciph_mode(void *msc_conn_ref)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_SET_CIPH, NULL);
+ return 0;
+}
+
+/* VLR informs us that the subscriber data has somehow been modified */
+static void msc_vlr_subscr_update(struct vlr_subscriber *subscr)
+{
+ DEBUGP(DGPRS, "%s\n", __func__);
+ /* FIXME */
+}
+
+static void msc_vlr_subscr_assoc(void *msc_conn_ref, struct vlr_subscriber *vsub)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ struct testvlr_priv *priv = fsi_priv(fi);
+ DEBUGP(DGPRS, "%s(%p, %s)\n", __func__, msc_conn_ref, vlr_sub_name(vsub));
+ priv->subscr = vsub;
+}
+
+/* operations that we need to implement for libvlr */
+static const struct vlr_ops test_vlr_ops = {
+ .tx_auth_req = msc_vlr_tx_auth_req,
+ .tx_auth_rej = msc_vlr_tx_auth_rej,
+ .tx_id_req = msc_vlr_tx_id_req,
+ .tx_lu_ack = msc_vlr_tx_lu_ack,
+ .tx_lu_rej = msc_vlr_tx_lu_rej,
+ .set_ciph_mode = msc_vlr_set_ciph_mode,
+ .subscr_update = msc_vlr_subscr_update,
+ .subscr_assoc = msc_vlr_subscr_assoc,
+};
+
+/***********************************************************************
+ * Actual test cases
+ ***********************************************************************/
+
+
+static struct osmo_fsm_inst *
+start_lu(enum testvlr_mode mode, uint32_t tmsi,
+ const char *imsi, const char *imei)
+{
+ struct testvlr_priv *vp;
+ struct osmo_fsm_inst *fi;
+
+ vp = talloc_zero(tall_bsc_ctx, struct testvlr_priv);
+ vp->mode = mode;
+ vp->tmsi = tmsi;
+ strncpy(vp->imsi, imsi, sizeof(vp->imsi));
+ strncpy(vp->imei, imei, sizeof(vp->imei));
+
+ fi = osmo_fsm_inst_alloc(&vlr_test_fsm, vp, vp, LOGL_DEBUG, vp->imsi);
+ osmo_fsm_inst_dispatch(fi, EVT_MS_TX_LU, NULL);
+ return fi;
+}
+
+/***********************************************************************
+ * Main / Misc
+ ***********************************************************************/
+
+/* dummy for debug.c */
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
+{
+ return subscr;
+}
+/* dummy for debug.c */
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
+{
+ return subscr;
+}
+
+static struct osmo_timer_list tmr;
+
+static void timer_cb(void *data)
+{
+ uint32_t tmsi = rand() % 1000000;
+ uint64_t imsi = 901790000000000 + tmsi;
+ char imsi_str[32];
+
+ snprintf(imsi_str, sizeof(imsi_str), "%lu", imsi);
+ //start_lu(MODE_AUTH_FAIL, tmsi, imsi_str, "23422342");
+ start_lu(MODE_SUCCESS_TMSI, tmsi, imsi_str, "23422342");
+ //start_lu(MODE_SUCCESS, tmsi, imsi_str, "23422342");
+ //start_upd_hlr_vlr(tall_bsc_ctx, tmsi, imsi_str);
+ //start_sub_pres_vlr(tall_bsc_ctx);
+ osmo_timer_schedule(&tmr, 8, 0);
+}
+
+static void sighdlr(int sig)
+{
+ switch (sig) {
+ case SIGUSR1:
+ talloc_report_full(tall_bsc_ctx, stderr);
+ break;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "tall_bsc_ctx");
+
+ signal(SIGUSR1, sighdlr);
+
+ osmo_init_logging(&log_info);
+
+ g_vlr = vlr_init(NULL, &test_vlr_ops, "localhost", 2222);
+ OSMO_ASSERT(g_vlr);
+ osmo_fsm_register(&vlr_test_fsm);
+ osmo_fsm_register(&test_sub_pres_vlr_fsm);
+ osmo_fsm_register(&test_upd_hlr_vlr_fsm);
+
+ g_vlr->cfg.alloc_tmsi = true;
+
+ tmr.cb = timer_cb;
+ timer_cb(NULL);
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ exit(0);
+}