diff options
Diffstat (limited to 'src/lu_fsm.c')
-rw-r--r-- | src/lu_fsm.c | 308 |
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); +} |