blob: bded4ef6fb29f5eda039381e3a96d4f70cdf3e37 [file] [log] [blame]
Neels Hofmeyrb2553eb2019-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
29#include <osmocom/gsupclient/ipa_name.h>
30#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. */
55 struct osmo_ipa_name vlr_name;
56
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. */
59 struct osmo_ipa_name via_proxy;
60};
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
133 if (!lu->vlr_name.len) {
134 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) {
139 lu_failure(lu, GMM_CAUSE_IMSI_UNKNOWN, "Subscriber does not exist");
140 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. */
166 if (lu->via_proxy.len) {
167 LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n",
168 lu->is_ps ? "SGSN number" : "VLR number",
169 osmo_ipa_name_to_str(&lu->vlr_name),
170 osmo_ipa_name_to_str(&lu->via_proxy));
171 } else {
172 LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n",
173 lu->is_ps ? "SGSN number" : "VLR number",
174 osmo_ipa_name_to_str(&lu->vlr_name));
175 }
176
177 if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name, lu->is_ps, &lu->via_proxy)) {
178 lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
179 lu->is_ps ? "SGSN number" : "VLR number");
180 return;
181 }
182
183 /* TODO: Subscriber allowed to roam in PLMN? */
184 /* TODO: Update RoutingInfo */
185 /* TODO: Reset Flag MS Purged (cs/ps) */
186 /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
187
188 lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
189}
190
191void lu_rx_gsup(struct osmo_gsup_req *req)
192{
193 struct lu *lu;
194 if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
195 return lu_start(req);
196
197 llist_for_each_entry(lu, &g_all_lu, entry) {
198 if (strcmp(lu->subscr.imsi, req->gsup.imsi))
199 continue;
200 if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
201 LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
202 osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
203 "LU does not accept GSUP rx");
204 }
205 return;
206 }
207 osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
208}
209
210static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
211{
212 struct lu *lu = fi->priv;
213 lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
214 return 0;
215}
216
217static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
218{
219 struct lu *lu = fi->priv;
220 if (lu->update_location_req)
221 osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
222 lu->update_location_req = NULL;
223 llist_del(&lu->entry);
224}
225
226static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
227{
228 /* Transmit Insert Data Request to the VLR */
229 struct lu *lu = fi->priv;
230 struct hlr_subscriber *subscr = &lu->subscr;
231 struct osmo_gsup_message gsup;
232 uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
233 uint8_t apn[APN_MAXLEN];
234
235 if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
236 subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
237 apn, sizeof(apn),
238 lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
239 lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
240 return;
241 }
242
243 if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false))
244 lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
245}
246
247void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
248{
249 struct lu *lu = fi->priv;
250 struct osmo_gsup_req *req;
251
252 switch (event) {
253 case LU_EV_RX_GSUP:
254 req = data;
255 break;
256 default:
257 OSMO_ASSERT(false);
258 }
259
260 switch (req->gsup.message_type) {
261 case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
262 osmo_gsup_req_free(req);
263 lu_success(lu);
264 break;
265
266 case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
267 lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
268 break;
269
270 default:
271 osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
272 break;
273 }
274}
275
276#define S(x) (1 << (x))
277
278static const struct osmo_fsm_state lu_fsm_states[] = {
279 [LU_ST_UNVALIDATED] = {
280 .name = "UNVALIDATED",
281 .out_state_mask = 0
282 | S(LU_ST_WAIT_INSERT_DATA_RESULT)
283 ,
284 },
285 [LU_ST_WAIT_INSERT_DATA_RESULT] = {
286 .name = "WAIT_INSERT_DATA_RESULT",
287 .in_event_mask = 0
288 | S(LU_EV_RX_GSUP)
289 ,
290 .onenter = lu_fsm_wait_insert_data_result_onenter,
291 .action = lu_fsm_wait_insert_data_result,
292 },
293};
294
295static struct osmo_fsm lu_fsm = {
296 .name = "lu",
297 .states = lu_fsm_states,
298 .num_states = ARRAY_SIZE(lu_fsm_states),
299 .log_subsys = DLU,
300 .event_names = lu_fsm_event_names,
301 .timer_cb = lu_fsm_timer_cb,
302 .cleanup = lu_fsm_cleanup,
303};
304
305static __attribute__((constructor)) void lu_fsm_init()
306{
307 OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
308}