aboutsummaryrefslogtreecommitdiffstats
path: root/src/lu_fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lu_fsm.c')
-rw-r--r--src/lu_fsm.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/lu_fsm.c b/src/lu_fsm.c
new file mode 100644
index 0000000..bded4ef
--- /dev/null
+++ b/src/lu_fsm.c
@@ -0,0 +1,308 @@
+/* Roughly following "Process Update_Location_HLR" of TS 09.02 */
+
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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/utils.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/apn.h>
+#include <osmocom/gsm/gsm48_ie.h>
+
+#include <osmocom/gsupclient/ipa_name.h>
+#include <osmocom/gsupclient/gsup_req.h>
+#include <osmocom/hlr/logging.h>
+#include <osmocom/hlr/hlr.h>
+#include <osmocom/hlr/gsup_server.h>
+
+#include <osmocom/hlr/db.h>
+
+#define LOG_LU(lu, level, fmt, args...) \
+ LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
+
+#define LOG_LU_REQ(lu, req, level, fmt, args...) \
+ LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \
+ osmo_gsup_message_type_name((req)->gsup.message_type), ##args)
+
+struct lu {
+ struct llist_head entry;
+ struct osmo_fsm_inst *fi;
+
+ struct osmo_gsup_req *update_location_req;
+
+ /* Subscriber state at time of initial Update Location Request */
+ struct hlr_subscriber subscr;
+ bool is_ps;
+
+ /* VLR requesting the LU. */
+ struct osmo_ipa_name vlr_name;
+
+ /* If the LU request was received via a proxy and not immediately from a local VLR, this indicates the closest
+ * peer that forwarded the GSUP message. */
+ struct osmo_ipa_name via_proxy;
+};
+LLIST_HEAD(g_all_lu);
+
+enum lu_fsm_event {
+ LU_EV_RX_GSUP,
+};
+
+enum lu_fsm_state {
+ LU_ST_UNVALIDATED,
+ LU_ST_WAIT_INSERT_DATA_RESULT,
+ LU_ST_WAIT_LOCATION_CANCEL_RESULT,
+};
+
+static const struct value_string lu_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LU_EV_RX_GSUP),
+ {}
+};
+
+static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
+ [LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 },
+ [LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
+};
+
+#define lu_state_chg(lu, state) \
+ osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
+
+static void lu_success(struct lu *lu)
+{
+ if (!lu->update_location_req)
+ LOG_LU(lu, LOGL_ERROR, "No request for this LU\n");
+ else
+ osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT, true);
+ lu->update_location_req = NULL;
+ osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+#define lu_failure(LU, CAUSE, log_msg, args...) do { \
+ if (!(LU)->update_location_req) \
+ LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \
+ else \
+ osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \
+ (LU)->update_location_req = NULL; \
+ osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \
+ } while(0)
+
+static struct osmo_fsm lu_fsm;
+
+static void lu_start(struct osmo_gsup_req *update_location_req)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu *lu;
+
+ OSMO_ASSERT(update_location_req);
+ OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
+
+ fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi);
+ OSMO_ASSERT(fi);
+
+ lu = talloc(fi, struct lu);
+ OSMO_ASSERT(lu);
+ fi->priv = lu;
+ *lu = (struct lu){
+ .fi = fi,
+ .update_location_req = update_location_req,
+ .vlr_name = update_location_req->source_name,
+ .via_proxy = update_location_req->via_proxy,
+ /* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */
+ .is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS),
+ };
+ llist_add(&lu->entry, &g_all_lu);
+
+ osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
+
+ if (!lu->vlr_name.len) {
+ lu_failure(lu, GMM_CAUSE_NET_FAIL, "LU without a VLR");
+ return;
+ }
+
+ if (db_subscr_get_by_imsi(g_hlr->dbc, update_location_req->gsup.imsi, &lu->subscr) < 0) {
+ lu_failure(lu, GMM_CAUSE_IMSI_UNKNOWN, "Subscriber does not exist");
+ return;
+ }
+
+ /* Check if subscriber is generally permitted on CS or PS
+ * service (as requested) */
+ if (!lu->is_ps && !lu->subscr.nam_cs) {
+ lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false");
+ return;
+ }
+ if (lu->is_ps && !lu->subscr.nam_ps) {
+ lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false");
+ return;
+ }
+
+ /* TODO: Set subscriber tracing = deactive in VLR/SGSN */
+
+#if 0
+ /* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */
+ if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) {
+ lu_op_tx_cancel_old(lu);
+ } else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) {
+ lu_op_tx_cancel_old(lu);
+ }
+#endif
+
+ /* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
+ if (lu->via_proxy.len) {
+ LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n",
+ lu->is_ps ? "SGSN number" : "VLR number",
+ osmo_ipa_name_to_str(&lu->vlr_name),
+ osmo_ipa_name_to_str(&lu->via_proxy));
+ } else {
+ LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n",
+ lu->is_ps ? "SGSN number" : "VLR number",
+ osmo_ipa_name_to_str(&lu->vlr_name));
+ }
+
+ if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name, lu->is_ps, &lu->via_proxy)) {
+ lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
+ lu->is_ps ? "SGSN number" : "VLR number");
+ return;
+ }
+
+ /* TODO: Subscriber allowed to roam in PLMN? */
+ /* TODO: Update RoutingInfo */
+ /* TODO: Reset Flag MS Purged (cs/ps) */
+ /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
+
+ lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
+}
+
+void lu_rx_gsup(struct osmo_gsup_req *req)
+{
+ struct lu *lu;
+ if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
+ return lu_start(req);
+
+ llist_for_each_entry(lu, &g_all_lu, entry) {
+ if (strcmp(lu->subscr.imsi, req->gsup.imsi))
+ continue;
+ if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
+ LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
+ "LU does not accept GSUP rx");
+ }
+ return;
+ }
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
+}
+
+static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct lu *lu = fi->priv;
+ lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
+ return 0;
+}
+
+static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lu *lu = fi->priv;
+ if (lu->update_location_req)
+ osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
+ lu->update_location_req = NULL;
+ llist_del(&lu->entry);
+}
+
+static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ /* Transmit Insert Data Request to the VLR */
+ struct lu *lu = fi->priv;
+ struct hlr_subscriber *subscr = &lu->subscr;
+ struct osmo_gsup_message gsup;
+ uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
+ uint8_t apn[APN_MAXLEN];
+
+ if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
+ subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
+ apn, sizeof(apn),
+ lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
+ lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
+ return;
+ }
+
+ if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false))
+ lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
+}
+
+void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct lu *lu = fi->priv;
+ struct osmo_gsup_req *req;
+
+ switch (event) {
+ case LU_EV_RX_GSUP:
+ req = data;
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+
+ switch (req->gsup.message_type) {
+ case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
+ osmo_gsup_req_free(req);
+ lu_success(lu);
+ break;
+
+ case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
+ lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
+ break;
+
+ default:
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
+ break;
+ }
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lu_fsm_states[] = {
+ [LU_ST_UNVALIDATED] = {
+ .name = "UNVALIDATED",
+ .out_state_mask = 0
+ | S(LU_ST_WAIT_INSERT_DATA_RESULT)
+ ,
+ },
+ [LU_ST_WAIT_INSERT_DATA_RESULT] = {
+ .name = "WAIT_INSERT_DATA_RESULT",
+ .in_event_mask = 0
+ | S(LU_EV_RX_GSUP)
+ ,
+ .onenter = lu_fsm_wait_insert_data_result_onenter,
+ .action = lu_fsm_wait_insert_data_result,
+ },
+};
+
+static struct osmo_fsm lu_fsm = {
+ .name = "lu",
+ .states = lu_fsm_states,
+ .num_states = ARRAY_SIZE(lu_fsm_states),
+ .log_subsys = DLU,
+ .event_names = lu_fsm_event_names,
+ .timer_cb = lu_fsm_timer_cb,
+ .cleanup = lu_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void lu_fsm_init()
+{
+ OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
+}