Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 1 | /* E-interface messaging over a GSUP connection */ |
| 2 | /* |
Vadim Yanitskiy | 999a593 | 2023-05-18 17:22:26 +0700 | [diff] [blame] | 3 | * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 4 | * All Rights Reserved |
| 5 | * |
| 6 | * SPDX-License-Identifier: AGPL-3.0+ |
| 7 | * |
| 8 | * Author: Neels Hofmeyr |
| 9 | * |
| 10 | * This program is free software; you can redistribute it and/or modify |
| 11 | * it under the terms of the GNU Affero General Public License as published by |
| 12 | * the Free Software Foundation; either version 3 of the License, or |
| 13 | * (at your option) any later version. |
| 14 | * |
| 15 | * This program is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | * GNU Affero General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU Affero General Public License |
| 21 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 22 | */ |
| 23 | |
| 24 | #include <osmocom/core/fsm.h> |
| 25 | #include <osmocom/gsupclient/gsup_client.h> |
| 26 | |
| 27 | #include <osmocom/msc/gsm_data.h> |
| 28 | #include <osmocom/msc/gsup_client_mux.h> |
| 29 | #include <osmocom/msc/e_link.h> |
| 30 | #include <osmocom/msc/msub.h> |
| 31 | #include <osmocom/msc/msc_roles.h> |
| 32 | #include <osmocom/msc/vlr.h> |
| 33 | #include <osmocom/msc/ran_infra.h> |
| 34 | #include <osmocom/msc/msc_a.h> |
| 35 | #include <osmocom/msc/msc_a_remote.h> |
| 36 | #include <osmocom/msc/msc_i.h> |
| 37 | #include <osmocom/msc/msc_i_remote.h> |
| 38 | #include <osmocom/msc/msc_t.h> |
| 39 | #include <osmocom/msc/msc_t_remote.h> |
| 40 | |
| 41 | #define LOG_E_LINK(e_link, level, fmt, args...) \ |
| 42 | LOGPFSML(e_link->msc_role, level, fmt, ##args) |
| 43 | |
| 44 | #define LOG_E_LINK_CAT(e_link, ss, level, fmt, args...) \ |
| 45 | LOGPFSMSL(e_link->msc_role, ss, level, fmt, ##args) |
| 46 | |
| 47 | void e_link_assign(struct e_link *e, struct osmo_fsm_inst *msc_role) |
| 48 | { |
| 49 | struct msc_role_common *c; |
| 50 | if (e->msc_role) { |
| 51 | c = e->msc_role->priv; |
| 52 | if (c->remote_to == e) { |
| 53 | c->remote_to = NULL; |
| 54 | msub_update_id(c->msub); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | c = msc_role->priv; |
| 59 | e->msc_role = msc_role; |
| 60 | c->remote_to = e; |
| 61 | |
| 62 | msub_update_id(c->msub); |
| 63 | LOG_E_LINK(e, LOGL_DEBUG, "Assigned E-link to %s\n", e_link_name(e)); |
| 64 | } |
| 65 | |
| 66 | struct e_link *e_link_alloc(struct gsup_client_mux *gcm, struct osmo_fsm_inst *msc_role, |
| 67 | const uint8_t *remote_name, size_t remote_name_len) |
| 68 | { |
| 69 | struct e_link *e; |
| 70 | struct msc_role_common *c = msc_role->priv; |
Neels Hofmeyr | e27fa15 | 2019-04-12 04:44:33 +0200 | [diff] [blame] | 71 | size_t use_len; |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 72 | |
| 73 | /* use msub as talloc parent, so we can move an e_link from msc_t to msc_i when it is established. */ |
| 74 | e = talloc_zero(c->msub, struct e_link); |
| 75 | if (!e) |
| 76 | return NULL; |
| 77 | |
| 78 | e->gcm = gcm; |
| 79 | |
Neels Hofmeyr | e27fa15 | 2019-04-12 04:44:33 +0200 | [diff] [blame] | 80 | /* FIXME: this is a braindamaged duality of char* and blob, which we can't seem to get rid of easily. |
| 81 | * See also osmo-hlr change I01a45900e14d41bcd338f50ad85d9fabf2c61405 which resolved this on the |
| 82 | * osmo-hlr side, but was abandoned. Not sure which way is the right solution. */ |
| 83 | /* To be able to include a terminating NUL character when sending the IPA name, append one if there is none yet. |
| 84 | * Current osmo-hlr needs the terminating NUL to be included. */ |
| 85 | use_len = remote_name_len; |
| 86 | if (remote_name[use_len-1] != '\0') |
| 87 | use_len++; |
| 88 | e->remote_name = talloc_size(e, use_len); |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 89 | OSMO_ASSERT(e->remote_name); |
| 90 | memcpy(e->remote_name, remote_name, remote_name_len); |
Neels Hofmeyr | e27fa15 | 2019-04-12 04:44:33 +0200 | [diff] [blame] | 91 | e->remote_name[use_len-1] = '\0'; |
| 92 | e->remote_name_len = use_len; |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 93 | |
| 94 | e_link_assign(e, msc_role); |
| 95 | return e; |
| 96 | } |
| 97 | |
| 98 | void e_link_free(struct e_link *e) |
| 99 | { |
| 100 | if (!e) |
| 101 | return; |
| 102 | if (e->msc_role) { |
| 103 | struct msc_role_common *c = e->msc_role->priv; |
| 104 | if (c->remote_to == e) |
| 105 | c->remote_to = NULL; |
| 106 | } |
| 107 | talloc_free(e); |
| 108 | } |
| 109 | |
| 110 | /* Set up IMSI, source and destination names in given gsup_msg struct. */ |
| 111 | int e_prep_gsup_msg(struct e_link *e, struct osmo_gsup_message *gsup_msg) |
| 112 | { |
| 113 | struct msc_role_common *c; |
| 114 | struct vlr_subscr *vsub; |
| 115 | const char *local_msc_name = NULL; |
| 116 | |
| 117 | if (e->gcm && e->gcm->gsup_client && e->gcm->gsup_client->ipa_dev) { |
| 118 | local_msc_name = e->gcm->gsup_client->ipa_dev->serno; |
| 119 | if (!local_msc_name) |
| 120 | local_msc_name = e->gcm->gsup_client->ipa_dev->unit_name; |
| 121 | } |
| 122 | |
| 123 | if (!local_msc_name) { |
| 124 | LOG_E_LINK(e, LOGL_ERROR, "Cannot prep E-interface GSUP message: no local MSC name defined\n"); |
| 125 | return -ENODEV; |
| 126 | } |
| 127 | |
| 128 | c = e->msc_role->priv; |
| 129 | vsub = c->msub->vsub; |
| 130 | *gsup_msg = (struct osmo_gsup_message){ |
| 131 | .message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC, |
| 132 | .source_name = (const uint8_t*)local_msc_name, |
Neels Hofmeyr | e27fa15 | 2019-04-12 04:44:33 +0200 | [diff] [blame] | 133 | .source_name_len = strlen(local_msc_name)+1, /* include terminating nul */ |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 134 | .destination_name = (const uint8_t*)e->remote_name, |
Neels Hofmeyr | e27fa15 | 2019-04-12 04:44:33 +0200 | [diff] [blame] | 135 | .destination_name_len = e->remote_name_len, /* the nul here is also included, from e_link_alloc() */ |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 136 | }; |
| 137 | |
| 138 | if (vsub) |
| 139 | OSMO_STRLCPY_ARRAY(gsup_msg->imsi, vsub->imsi); |
| 140 | return 0; |
| 141 | } |
| 142 | |
| 143 | int e_tx(struct e_link *e, const struct osmo_gsup_message *gsup_msg) |
| 144 | { |
| 145 | LOG_E_LINK_CAT(e, DLGSUP, LOGL_DEBUG, "Tx GSUP %s to %s\n", |
| 146 | osmo_gsup_message_type_name(gsup_msg->message_type), |
| 147 | e_link_name(e)); |
| 148 | return gsup_client_mux_tx(e->gcm, gsup_msg); |
| 149 | } |
| 150 | |
| 151 | const char *e_link_name(struct e_link *e) |
| 152 | { |
| 153 | return osmo_escape_str((const char*)e->remote_name, e->remote_name_len); |
| 154 | } |
| 155 | |
| 156 | static struct msub *msc_new_msc_t_for_handover_request(struct gsm_network *net, |
| 157 | const struct osmo_gsup_message *gsup_msg) |
| 158 | { |
| 159 | struct ran_infra *ran; |
| 160 | struct msub *msub; |
| 161 | struct msc_a *msc_a; |
| 162 | struct vlr_subscr *vsub; |
| 163 | |
| 164 | switch (gsup_msg->an_apdu.access_network_proto) { |
| 165 | case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006: |
| 166 | ran = &msc_ran_infra[OSMO_RAT_GERAN_A]; |
| 167 | break; |
| 168 | case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413: |
| 169 | ran = &msc_ran_infra[OSMO_RAT_UTRAN_IU]; |
| 170 | break; |
| 171 | default: |
| 172 | ran = NULL; |
| 173 | break; |
| 174 | } |
| 175 | |
| 176 | if (!ran || !ran->ran_dec_l2) { |
| 177 | LOGP(DLGSUP, LOGL_ERROR, "Cannot handle AN-proto %s\n", |
| 178 | an_proto_name(gsup_msg->an_apdu.access_network_proto)); |
| 179 | return NULL; |
| 180 | } |
| 181 | |
| 182 | msub = msub_alloc(net); |
| 183 | |
| 184 | /* To properly compose GSUP messages going back to the remote peer, make sure the incoming IMSI is set in a |
| 185 | * vlr_subscr associated with the msub. */ |
| 186 | vsub = vlr_subscr_find_or_create_by_imsi(net->vlr, gsup_msg->imsi, __func__, NULL); |
| 187 | msub_set_vsub(msub, vsub); |
| 188 | vlr_subscr_put(vsub, __func__); |
| 189 | |
| 190 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n"); |
| 191 | |
| 192 | msc_a = msc_a_remote_alloc(msub, ran, gsup_msg->source_name, gsup_msg->source_name_len); |
| 193 | if (!msc_a) { |
| 194 | osmo_fsm_inst_term(msub->fi, OSMO_FSM_TERM_REQUEST, NULL); |
| 195 | return NULL; |
| 196 | } |
| 197 | |
| 198 | LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n"); |
| 199 | return msub; |
| 200 | } |
| 201 | |
| 202 | static bool name_matches(const uint8_t *name, size_t len, const uint8_t *match_name, size_t match_len) |
| 203 | { |
| 204 | if (!match_name) |
| 205 | return !name || !len; |
| 206 | if (len != match_len) |
| 207 | return false; |
| 208 | return memcmp(name, match_name, len) == 0; |
| 209 | } |
| 210 | |
| 211 | static bool e_link_matches_gsup_msg_source_name(const struct e_link *e, const struct osmo_gsup_message *gsup_msg) |
| 212 | { |
| 213 | return name_matches(gsup_msg->source_name, gsup_msg->source_name_len, e->remote_name, e->remote_name_len); |
| 214 | } |
| 215 | |
| 216 | static int msc_a_i_t_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg) |
| 217 | { |
| 218 | struct gsm_network *net = data; |
| 219 | struct vlr_instance *vlr = net->vlr; |
| 220 | struct vlr_subscr *vsub; |
| 221 | struct msub *msub; |
| 222 | struct osmo_fsm_inst *msc_role = NULL; |
| 223 | struct e_link *e; |
| 224 | struct msc_role_common *c; |
| 225 | int i; |
| 226 | |
| 227 | OSMO_ASSERT(net); |
| 228 | |
| 229 | vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__); |
| 230 | if (vsub) |
| 231 | LOGP(DLGSUP, LOGL_DEBUG, "Found VLR entry for IMSI %s\n", gsup_msg->imsi); |
| 232 | |
| 233 | msub = msub_for_vsub(vsub); |
| 234 | if (msub) |
| 235 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Found already attached subscriber for IMSI %s\n", |
| 236 | gsup_msg->imsi); |
| 237 | |
| 238 | if (vsub) { |
| 239 | vlr_subscr_put(vsub, __func__); |
| 240 | vsub = NULL; |
| 241 | } |
| 242 | |
| 243 | /* Only for an incoming Handover Request: create a new remote-MSC-A as proxy for the MSC-A that is sending the |
| 244 | * Handover Request */ |
| 245 | if (!msub && gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) { |
| 246 | msub = msc_new_msc_t_for_handover_request(net, gsup_msg); |
| 247 | } |
| 248 | |
| 249 | if (!msub) { |
| 250 | LOGP(DLGSUP, LOGL_ERROR, "%s: Cannot find subscriber for IMSI %s\n", |
| 251 | __func__, osmo_quote_str(gsup_msg->imsi, -1)); |
| 252 | return -EINVAL; |
| 253 | } |
| 254 | |
| 255 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Rx GSUP %s\n", osmo_gsup_message_type_name(gsup_msg->message_type)); |
| 256 | |
| 257 | e = NULL; |
| 258 | for (i = 0; i < ARRAY_SIZE(msub->role); i++) { |
| 259 | msc_role = msub->role[i]; |
| 260 | if (!msc_role) { |
| 261 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "No %s\n", msc_role_name(i)); |
| 262 | continue; |
| 263 | } |
| 264 | c = msc_role->priv; |
| 265 | if (!c->remote_to) { |
| 266 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has no remote\n", msc_role_name(i)); |
| 267 | continue; |
| 268 | } |
| 269 | if (!e_link_matches_gsup_msg_source_name(c->remote_to, gsup_msg)) { |
| 270 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has remote to mismatching %s\n", msc_role_name(i), |
| 271 | c->remote_to->remote_name); |
| 272 | continue; |
| 273 | } |
| 274 | /* Found a match. */ |
| 275 | e = c->remote_to; |
| 276 | break; |
| 277 | } |
| 278 | |
| 279 | if (!e) { |
| 280 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_ERROR, |
| 281 | "There is no E link that matches: Rx GSUP %s from %s\n", |
| 282 | osmo_gsup_message_type_name(gsup_msg->message_type), |
| 283 | osmo_quote_str((const char*)gsup_msg->source_name, gsup_msg->source_name_len)); |
| 284 | return -EINVAL; |
| 285 | } |
| 286 | |
| 287 | LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, |
| 288 | "Rx GSUP %s from %s %s\n", |
| 289 | osmo_gsup_message_type_name(gsup_msg->message_type), |
| 290 | msc_role_name(c->role), |
| 291 | e_link_name(e)); |
| 292 | |
| 293 | return osmo_fsm_inst_dispatch(msc_role, MSC_REMOTE_EV_RX_GSUP, (void*)gsup_msg); |
| 294 | } |
| 295 | |
| 296 | void msc_a_i_t_gsup_init(struct gsm_network *net) |
| 297 | { |
| 298 | OSMO_ASSERT(net->gcm); |
| 299 | OSMO_ASSERT(net->vlr); |
| 300 | |
| 301 | net->gcm->rx_cb[OSMO_GSUP_MESSAGE_CLASS_INTER_MSC] = (struct gsup_client_mux_rx_cb){ |
| 302 | .func = msc_a_i_t_gsup_rx, |
| 303 | .data = net, |
| 304 | }; |
| 305 | } |
| 306 | |
| 307 | int gsup_msg_assign_an_apdu(struct osmo_gsup_message *gsup_msg, struct an_apdu *an_apdu) |
| 308 | { |
| 309 | if (!an_apdu) { |
| 310 | LOGP(DLGSUP, LOGL_ERROR, "Cannot assign NULL AN-APDU\n"); |
| 311 | return -EINVAL; |
| 312 | } |
| 313 | |
| 314 | gsup_msg->an_apdu = (struct osmo_gsup_an_apdu){ |
| 315 | .access_network_proto = an_apdu->an_proto, |
| 316 | }; |
| 317 | |
| 318 | if (an_apdu->msg) { |
| 319 | gsup_msg->an_apdu.data = msgb_l2(an_apdu->msg); |
| 320 | gsup_msg->an_apdu.data_len = msgb_l2len(an_apdu->msg); |
| 321 | if (!gsup_msg->an_apdu.data || !gsup_msg->an_apdu.data_len) { |
| 322 | LOGP(DLGSUP, LOGL_ERROR, "Cannot assign AN-APDU without msg->l2 to GSUP message: %s\n", |
| 323 | msgb_hexdump(an_apdu->msg)); |
| 324 | return -EINVAL; |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | /* We are composing a struct osmo_gsup_msg from the osmo-msc internal struct an_apdu. The an_apdu may contain |
| 329 | * additional info in form of a partly filled an_apdu->e_info. Make sure that data ends up in the resulting full |
| 330 | * osmo_gsup_message. */ |
| 331 | if (an_apdu->e_info) { |
| 332 | const struct osmo_gsup_message *s = an_apdu->e_info; |
| 333 | |
| 334 | gsup_msg->msisdn_enc = s->msisdn_enc; |
| 335 | gsup_msg->msisdn_enc_len = s->msisdn_enc_len; |
| 336 | |
| 337 | if (s->cause_rr_set) { |
| 338 | gsup_msg->cause_rr = s->cause_rr; |
| 339 | gsup_msg->cause_rr_set = true; |
| 340 | } |
| 341 | if (s->cause_bssap_set) { |
| 342 | gsup_msg->cause_bssap = s->cause_bssap; |
| 343 | gsup_msg->cause_bssap_set = true; |
| 344 | } |
| 345 | if (s->cause_sm) |
| 346 | gsup_msg->cause_sm = s->cause_sm; |
| 347 | } |
| 348 | return 0; |
| 349 | } |
| 350 | |
| 351 | /* Allocate a new msgb to contain the gsup_msg->an_apdu's data as l2h. |
| 352 | * The msgb will have sufficient headroom to be passed down a RAN peer's SCCP user SAP. */ |
| 353 | struct msgb *gsup_msg_to_msgb(const struct osmo_gsup_message *gsup_msg) |
| 354 | { |
| 355 | struct msgb *pdu; |
| 356 | const uint8_t *pdu_data = gsup_msg->an_apdu.data; |
| 357 | uint8_t pdu_len = gsup_msg->an_apdu.data_len; |
| 358 | |
| 359 | if (!pdu_data || !pdu_len) |
| 360 | return NULL; |
| 361 | |
| 362 | /* Strictly speaking this is not limited to BSSMAP, but why not just use those sizes. */ |
| 363 | pdu = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "AN-APDU from gsup_msg"); |
| 364 | |
| 365 | pdu->l2h = msgb_put(pdu, pdu_len); |
| 366 | memcpy(pdu->l2h, pdu_data, pdu_len); |
| 367 | return pdu; |
| 368 | } |
| 369 | |
| 370 | /* Compose a struct an_apdu from the data found in gsup_msg. gsup_msg_to_msgb() is used to wrap the data in a static |
| 371 | * msgb, so the returned an_apdu->msg must be freed if not NULL. */ |
| 372 | void gsup_msg_to_an_apdu(struct an_apdu *an_apdu, const struct osmo_gsup_message *gsup_msg) |
| 373 | { |
| 374 | *an_apdu = (struct an_apdu){ |
| 375 | .an_proto = gsup_msg->an_apdu.access_network_proto, |
| 376 | .msg = gsup_msg_to_msgb(gsup_msg), |
| 377 | .e_info = gsup_msg, |
| 378 | }; |
| 379 | } |