Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 1 | /* 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 Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 29 | #include <osmocom/gsupclient/gsup_peer_id.h> |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 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 | |
| 44 | struct 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 Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 55 | struct osmo_gsup_peer_id vlr_name; |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 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. */ |
Neels Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 59 | struct osmo_gsup_peer_id via_proxy; |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 60 | }; |
| 61 | LLIST_HEAD(g_all_lu); |
| 62 | |
| 63 | enum lu_fsm_event { |
| 64 | LU_EV_RX_GSUP, |
| 65 | }; |
| 66 | |
| 67 | enum lu_fsm_state { |
| 68 | LU_ST_UNVALIDATED, |
| 69 | LU_ST_WAIT_INSERT_DATA_RESULT, |
| 70 | LU_ST_WAIT_LOCATION_CANCEL_RESULT, |
| 71 | }; |
| 72 | |
| 73 | static const struct value_string lu_fsm_event_names[] = { |
| 74 | OSMO_VALUE_STRING(LU_EV_RX_GSUP), |
| 75 | {} |
| 76 | }; |
| 77 | |
| 78 | static 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 | |
| 86 | static 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 | |
| 105 | static struct osmo_fsm lu_fsm; |
| 106 | |
| 107 | static 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 Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 133 | if (osmo_gsup_peer_id_is_empty(&lu->vlr_name)) { |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 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 */ |
| 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 Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 166 | if (!osmo_gsup_peer_id_is_empty(&lu->via_proxy)) { |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 167 | LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n", |
| 168 | lu->is_ps ? "SGSN number" : "VLR number", |
Neels Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 169 | osmo_gsup_peer_id_to_str(&lu->vlr_name), |
| 170 | osmo_gsup_peer_id_to_str(&lu->via_proxy)); |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 171 | } else { |
| 172 | LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n", |
| 173 | lu->is_ps ? "SGSN number" : "VLR number", |
Neels Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 174 | osmo_gsup_peer_id_to_str(&lu->vlr_name)); |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 175 | } |
| 176 | |
Neels Hofmeyr | 008ce4b | 2019-12-04 01:04:32 +0100 | [diff] [blame^] | 177 | if (osmo_gsup_peer_id_is_empty(&lu->vlr_name) |
| 178 | || (lu->vlr_name.type != OSMO_GSUP_PEER_ID_IPA_NAME)) { |
| 179 | lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for vlr_name: %s", |
| 180 | osmo_gsup_peer_id_type_name(lu->vlr_name.type)); |
| 181 | return; |
| 182 | } |
| 183 | if (!osmo_gsup_peer_id_is_empty(&lu->via_proxy) && (lu->via_proxy.type != OSMO_GSUP_PEER_ID_IPA_NAME)) { |
| 184 | lu_failure(lu, GMM_CAUSE_PROTO_ERR_UNSPEC, "Unsupported GSUP peer id type for via_proxy: %s", |
| 185 | osmo_gsup_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_gsup_peer_id_is_empty(&lu->via_proxy)? NULL : &lu->via_proxy.ipa_name)) { |
Neels Hofmeyr | f13a8bc | 2019-11-20 02:36:45 +0100 | [diff] [blame] | 190 | 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 | |
| 203 | void 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 | |
| 222 | static 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 | |
| 229 | static 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 | |
| 238 | static 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; |
| 244 | uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN]; |
| 245 | uint8_t apn[APN_MAXLEN]; |
| 246 | |
| 247 | if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, |
| 248 | subscr->msisdn, msisdn_enc, sizeof(msisdn_enc), |
| 249 | apn, sizeof(apn), |
| 250 | lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) { |
| 251 | lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message"); |
| 252 | return; |
| 253 | } |
| 254 | |
| 255 | if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false)) |
| 256 | lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type)); |
| 257 | } |
| 258 | |
| 259 | void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 260 | { |
| 261 | struct lu *lu = fi->priv; |
| 262 | struct osmo_gsup_req *req; |
| 263 | |
| 264 | switch (event) { |
| 265 | case LU_EV_RX_GSUP: |
| 266 | req = data; |
| 267 | break; |
| 268 | default: |
| 269 | OSMO_ASSERT(false); |
| 270 | } |
| 271 | |
| 272 | switch (req->gsup.message_type) { |
| 273 | case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: |
| 274 | osmo_gsup_req_free(req); |
| 275 | lu_success(lu); |
| 276 | break; |
| 277 | |
| 278 | case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: |
| 279 | lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type)); |
| 280 | break; |
| 281 | |
| 282 | default: |
| 283 | osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state"); |
| 284 | break; |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | #define S(x) (1 << (x)) |
| 289 | |
| 290 | static const struct osmo_fsm_state lu_fsm_states[] = { |
| 291 | [LU_ST_UNVALIDATED] = { |
| 292 | .name = "UNVALIDATED", |
| 293 | .out_state_mask = 0 |
| 294 | | S(LU_ST_WAIT_INSERT_DATA_RESULT) |
| 295 | , |
| 296 | }, |
| 297 | [LU_ST_WAIT_INSERT_DATA_RESULT] = { |
| 298 | .name = "WAIT_INSERT_DATA_RESULT", |
| 299 | .in_event_mask = 0 |
| 300 | | S(LU_EV_RX_GSUP) |
| 301 | , |
| 302 | .onenter = lu_fsm_wait_insert_data_result_onenter, |
| 303 | .action = lu_fsm_wait_insert_data_result, |
| 304 | }, |
| 305 | }; |
| 306 | |
| 307 | static struct osmo_fsm lu_fsm = { |
| 308 | .name = "lu", |
| 309 | .states = lu_fsm_states, |
| 310 | .num_states = ARRAY_SIZE(lu_fsm_states), |
| 311 | .log_subsys = DLU, |
| 312 | .event_names = lu_fsm_event_names, |
| 313 | .timer_cb = lu_fsm_timer_cb, |
| 314 | .cleanup = lu_fsm_cleanup, |
| 315 | }; |
| 316 | |
| 317 | static __attribute__((constructor)) void lu_fsm_init() |
| 318 | { |
| 319 | OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0); |
| 320 | } |