blob: 8599f590a834e3a87840230c145888208b14da56 [file] [log] [blame]
Neels Hofmeyrad868e22019-11-20 02:36:45 +01001/* Roughly following "Process Update_Location_HLR" of TS 09.02 */
2
3/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
4 *
5 * All Rights Reserved
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22#include <osmocom/core/utils.h>
23#include <osmocom/core/tdef.h>
24#include <osmocom/core/linuxlist.h>
25#include <osmocom/core/fsm.h>
26#include <osmocom/gsm/apn.h>
27#include <osmocom/gsm/gsm48_ie.h>
28
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +010029#include <osmocom/gsupclient/cni_peer_id.h>
Neels Hofmeyrad868e22019-11-20 02:36:45 +010030#include <osmocom/gsupclient/gsup_req.h>
31#include <osmocom/hlr/logging.h>
32#include <osmocom/hlr/hlr.h>
33#include <osmocom/hlr/gsup_server.h>
34
35#include <osmocom/hlr/db.h>
36
37#define LOG_LU(lu, level, fmt, args...) \
38 LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
39
40#define LOG_LU_REQ(lu, req, level, fmt, args...) \
41 LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \
42 osmo_gsup_message_type_name((req)->gsup.message_type), ##args)
43
44struct lu {
45 struct llist_head entry;
46 struct osmo_fsm_inst *fi;
47
48 struct osmo_gsup_req *update_location_req;
49
50 /* Subscriber state at time of initial Update Location Request */
51 struct hlr_subscriber subscr;
52 bool is_ps;
53
54 /* VLR requesting the LU. */
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +010055 struct osmo_cni_peer_id vlr_name;
Neels Hofmeyrad868e22019-11-20 02:36:45 +010056
57 /* If the LU request was received via a proxy and not immediately from a local VLR, this indicates the closest
58 * peer that forwarded the GSUP message. */
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +010059 struct osmo_cni_peer_id via_proxy;
Neels Hofmeyrad868e22019-11-20 02:36:45 +010060};
61LLIST_HEAD(g_all_lu);
62
63enum lu_fsm_event {
64 LU_EV_RX_GSUP,
65};
66
67enum lu_fsm_state {
68 LU_ST_UNVALIDATED,
69 LU_ST_WAIT_INSERT_DATA_RESULT,
70 LU_ST_WAIT_LOCATION_CANCEL_RESULT,
71};
72
73static const struct value_string lu_fsm_event_names[] = {
74 OSMO_VALUE_STRING(LU_EV_RX_GSUP),
75 {}
76};
77
78static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
79 [LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 },
80 [LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
81};
82
83#define lu_state_chg(lu, state) \
84 osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
85
86static void lu_success(struct lu *lu)
87{
88 if (!lu->update_location_req)
89 LOG_LU(lu, LOGL_ERROR, "No request for this LU\n");
90 else
91 osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT, true);
92 lu->update_location_req = NULL;
93 osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL);
94}
95
96#define lu_failure(LU, CAUSE, log_msg, args...) do { \
97 if (!(LU)->update_location_req) \
98 LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \
99 else \
100 osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \
101 (LU)->update_location_req = NULL; \
102 osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \
103 } while(0)
104
105static struct osmo_fsm lu_fsm;
106
107static void lu_start(struct osmo_gsup_req *update_location_req)
108{
109 struct osmo_fsm_inst *fi;
110 struct lu *lu;
111
112 OSMO_ASSERT(update_location_req);
113 OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
114
115 fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi);
116 OSMO_ASSERT(fi);
117
118 lu = talloc(fi, struct lu);
119 OSMO_ASSERT(lu);
120 fi->priv = lu;
121 *lu = (struct lu){
122 .fi = fi,
123 .update_location_req = update_location_req,
124 .vlr_name = update_location_req->source_name,
125 .via_proxy = update_location_req->via_proxy,
126 /* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */
127 .is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS),
128 };
129 llist_add(&lu->entry, &g_all_lu);
130
131 osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
132
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +0100133 if (osmo_cni_peer_id_is_empty(&lu->vlr_name)) {
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100134 lu_failure(lu, GMM_CAUSE_NET_FAIL, "LU without a VLR");
135 return;
136 }
137
138 if (db_subscr_get_by_imsi(g_hlr->dbc, update_location_req->gsup.imsi, &lu->subscr) < 0) {
Alexander Couzens268a33e2020-01-12 00:48:07 +0100139 lu_failure(lu, g_hlr->reject_cause, "Subscriber does not exist");
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100140 return;
141 }
142
143 /* Check if subscriber is generally permitted on CS or PS
144 * service (as requested) */
145 if (!lu->is_ps && !lu->subscr.nam_cs) {
146 lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false");
147 return;
148 }
149 if (lu->is_ps && !lu->subscr.nam_ps) {
150 lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false");
151 return;
152 }
153
154 /* TODO: Set subscriber tracing = deactive in VLR/SGSN */
155
156#if 0
157 /* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */
158 if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) {
159 lu_op_tx_cancel_old(lu);
160 } else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) {
161 lu_op_tx_cancel_old(lu);
162 }
163#endif
164
165 /* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +0100166 if (!osmo_cni_peer_id_is_empty(&lu->via_proxy)) {
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100167 LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n",
168 lu->is_ps ? "SGSN number" : "VLR number",
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +0100169 osmo_cni_peer_id_to_str(&lu->vlr_name),
170 osmo_cni_peer_id_to_str(&lu->via_proxy));
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100171 } else {
172 LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n",
173 lu->is_ps ? "SGSN number" : "VLR number",
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +0100174 osmo_cni_peer_id_to_str(&lu->vlr_name));
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100175 }
176
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +0100177 if (osmo_cni_peer_id_is_empty(&lu->vlr_name)
178 || (lu->vlr_name.type != OSMO_CNI_PEER_ID_IPA_NAME)) {
179 lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for vlr_name: %s",
180 osmo_cni_peer_id_type_name(lu->vlr_name.type));
181 return;
182 }
183 if (!osmo_cni_peer_id_is_empty(&lu->via_proxy) && (lu->via_proxy.type != OSMO_CNI_PEER_ID_IPA_NAME)) {
184 lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for via_proxy: %s",
185 osmo_cni_peer_id_type_name(lu->via_proxy.type));
186 return;
187 }
188 if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name.ipa_name, lu->is_ps,
189 osmo_cni_peer_id_is_empty(&lu->via_proxy)? NULL : &lu->via_proxy.ipa_name)) {
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100190 lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
191 lu->is_ps ? "SGSN number" : "VLR number");
192 return;
193 }
194
195 /* TODO: Subscriber allowed to roam in PLMN? */
196 /* TODO: Update RoutingInfo */
197 /* TODO: Reset Flag MS Purged (cs/ps) */
198 /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
199
200 lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
201}
202
203void lu_rx_gsup(struct osmo_gsup_req *req)
204{
205 struct lu *lu;
206 if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
207 return lu_start(req);
208
209 llist_for_each_entry(lu, &g_all_lu, entry) {
210 if (strcmp(lu->subscr.imsi, req->gsup.imsi))
211 continue;
212 if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
213 LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
214 osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
215 "LU does not accept GSUP rx");
216 }
217 return;
218 }
219 osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
220}
221
222static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
223{
224 struct lu *lu = fi->priv;
225 lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
226 return 0;
227}
228
229static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
230{
231 struct lu *lu = fi->priv;
232 if (lu->update_location_req)
233 osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
234 lu->update_location_req = NULL;
235 llist_del(&lu->entry);
236}
237
238static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
239{
240 /* Transmit Insert Data Request to the VLR */
241 struct lu *lu = fi->priv;
242 struct hlr_subscriber *subscr = &lu->subscr;
243 struct osmo_gsup_message gsup;
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100244
245 if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
Alexander Couzens29898732023-04-11 17:53:33 +0200246 subscr->msisdn,
247 lu->is_ps ? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS,
248 OTC_SELECT)) {
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100249 lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
250 return;
251 }
252
253 if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false))
254 lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
255}
256
257void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
258{
259 struct lu *lu = fi->priv;
260 struct osmo_gsup_req *req;
261
262 switch (event) {
263 case LU_EV_RX_GSUP:
264 req = data;
265 break;
266 default:
267 OSMO_ASSERT(false);
268 }
269
270 switch (req->gsup.message_type) {
271 case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
272 osmo_gsup_req_free(req);
273 lu_success(lu);
274 break;
275
276 case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
277 lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
Vadim Yanitskiy1c23f302023-07-05 00:15:11 +0700278 osmo_gsup_req_free(req);
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100279 break;
280
281 default:
282 osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
283 break;
284 }
285}
286
287#define S(x) (1 << (x))
288
289static const struct osmo_fsm_state lu_fsm_states[] = {
290 [LU_ST_UNVALIDATED] = {
291 .name = "UNVALIDATED",
292 .out_state_mask = 0
293 | S(LU_ST_WAIT_INSERT_DATA_RESULT)
294 ,
295 },
296 [LU_ST_WAIT_INSERT_DATA_RESULT] = {
297 .name = "WAIT_INSERT_DATA_RESULT",
298 .in_event_mask = 0
299 | S(LU_EV_RX_GSUP)
300 ,
301 .onenter = lu_fsm_wait_insert_data_result_onenter,
302 .action = lu_fsm_wait_insert_data_result,
303 },
304};
305
306static struct osmo_fsm lu_fsm = {
307 .name = "lu",
308 .states = lu_fsm_states,
309 .num_states = ARRAY_SIZE(lu_fsm_states),
310 .log_subsys = DLU,
311 .event_names = lu_fsm_event_names,
312 .timer_cb = lu_fsm_timer_cb,
313 .cleanup = lu_fsm_cleanup,
314};
315
Harald Welte7a476532022-11-03 11:38:41 +0100316static __attribute__((constructor)) void lu_fsm_init(void)
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100317{
318 OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
319}