/* E-interface messaging over a GSUP connection */ /* * (C) 2019 by sysmocom - s.f.m.c. GmbH * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ * * Author: Neels Hofmeyr * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_E_LINK(e_link, level, fmt, args...) \ LOGPFSML(e_link->msc_role, level, fmt, ##args) #define LOG_E_LINK_CAT(e_link, ss, level, fmt, args...) \ LOGPFSMSL(e_link->msc_role, ss, level, fmt, ##args) void e_link_assign(struct e_link *e, struct osmo_fsm_inst *msc_role) { struct msc_role_common *c; if (e->msc_role) { c = e->msc_role->priv; if (c->remote_to == e) { c->remote_to = NULL; msub_update_id(c->msub); } } c = msc_role->priv; e->msc_role = msc_role; c->remote_to = e; msub_update_id(c->msub); LOG_E_LINK(e, LOGL_DEBUG, "Assigned E-link to %s\n", e_link_name(e)); } struct e_link *e_link_alloc(struct gsup_client_mux *gcm, struct osmo_fsm_inst *msc_role, const uint8_t *remote_name, size_t remote_name_len) { struct e_link *e; struct msc_role_common *c = msc_role->priv; size_t use_len; /* use msub as talloc parent, so we can move an e_link from msc_t to msc_i when it is established. */ e = talloc_zero(c->msub, struct e_link); if (!e) return NULL; e->gcm = gcm; /* FIXME: this is a braindamaged duality of char* and blob, which we can't seem to get rid of easily. * See also osmo-hlr change I01a45900e14d41bcd338f50ad85d9fabf2c61405 which resolved this on the * osmo-hlr side, but was abandoned. Not sure which way is the right solution. */ /* To be able to include a terminating NUL character when sending the IPA name, append one if there is none yet. * Current osmo-hlr needs the terminating NUL to be included. */ use_len = remote_name_len; if (remote_name[use_len-1] != '\0') use_len++; e->remote_name = talloc_size(e, use_len); OSMO_ASSERT(e->remote_name); memcpy(e->remote_name, remote_name, remote_name_len); e->remote_name[use_len-1] = '\0'; e->remote_name_len = use_len; e_link_assign(e, msc_role); return e; } void e_link_free(struct e_link *e) { if (!e) return; if (e->msc_role) { struct msc_role_common *c = e->msc_role->priv; if (c->remote_to == e) c->remote_to = NULL; } talloc_free(e); } /* Set up IMSI, source and destination names in given gsup_msg struct. */ int e_prep_gsup_msg(struct e_link *e, struct osmo_gsup_message *gsup_msg) { struct msc_role_common *c; struct vlr_subscr *vsub; const char *local_msc_name = NULL; if (e->gcm && e->gcm->gsup_client) { const struct ipaccess_unit *ipa_dev = osmo_gsup_client_get_ipaccess_unit(e->gcm->gsup_client); if (ipa_dev) { local_msc_name = ipa_dev->serno; if (!local_msc_name) local_msc_name = ipa_dev->unit_name; } } if (!local_msc_name) { LOG_E_LINK(e, LOGL_ERROR, "Cannot prep E-interface GSUP message: no local MSC name defined\n"); return -ENODEV; } c = e->msc_role->priv; vsub = c->msub->vsub; *gsup_msg = (struct osmo_gsup_message){ .message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC, .source_name = (const uint8_t*)local_msc_name, .source_name_len = strlen(local_msc_name)+1, /* include terminating nul */ .destination_name = (const uint8_t*)e->remote_name, .destination_name_len = e->remote_name_len, /* the nul here is also included, from e_link_alloc() */ }; if (vsub) OSMO_STRLCPY_ARRAY(gsup_msg->imsi, vsub->imsi); return 0; } int e_tx(struct e_link *e, const struct osmo_gsup_message *gsup_msg) { LOG_E_LINK_CAT(e, DLGSUP, LOGL_DEBUG, "Tx GSUP %s to %s\n", osmo_gsup_message_type_name(gsup_msg->message_type), e_link_name(e)); return gsup_client_mux_tx(e->gcm, gsup_msg); } const char *e_link_name(struct e_link *e) { return osmo_escape_str((const char*)e->remote_name, e->remote_name_len); } static struct msub *msc_new_msc_t_for_handover_request(struct gsm_network *net, const struct osmo_gsup_message *gsup_msg) { struct ran_infra *ran; struct msub *msub; struct msc_a *msc_a; struct vlr_subscr *vsub; switch (gsup_msg->an_apdu.access_network_proto) { case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006: ran = &msc_ran_infra[OSMO_RAT_GERAN_A]; break; case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413: ran = &msc_ran_infra[OSMO_RAT_UTRAN_IU]; break; default: ran = NULL; break; } if (!ran || !ran->ran_dec_l2) { LOGP(DLGSUP, LOGL_ERROR, "Cannot handle AN-proto %s\n", an_proto_name(gsup_msg->an_apdu.access_network_proto)); return NULL; } msub = msub_alloc(net); /* To properly compose GSUP messages going back to the remote peer, make sure the incoming IMSI is set in a * vlr_subscr associated with the msub. */ vsub = vlr_subscr_find_or_create_by_imsi(net->vlr, gsup_msg->imsi, __func__, NULL); msub_set_vsub(msub, vsub); vlr_subscr_put(vsub, __func__); LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n"); msc_a = msc_a_remote_alloc(msub, ran, gsup_msg->source_name, gsup_msg->source_name_len); if (!msc_a) { osmo_fsm_inst_term(msub->fi, OSMO_FSM_TERM_REQUEST, NULL); return NULL; } LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n"); return msub; } static bool name_matches(const uint8_t *name, size_t len, const uint8_t *match_name, size_t match_len) { if (!match_name) return !name || !len; if (len != match_len) return false; return memcmp(name, match_name, len) == 0; } static bool e_link_matches_gsup_msg_source_name(const struct e_link *e, const struct osmo_gsup_message *gsup_msg) { return name_matches(gsup_msg->source_name, gsup_msg->source_name_len, e->remote_name, e->remote_name_len); } static int msc_a_i_t_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg) { struct gsm_network *net = data; struct vlr_instance *vlr = net->vlr; struct vlr_subscr *vsub; struct msub *msub; struct osmo_fsm_inst *msc_role = NULL; struct e_link *e; struct msc_role_common *c; int i; OSMO_ASSERT(net); vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__); if (vsub) LOGP(DLGSUP, LOGL_DEBUG, "Found VLR entry for IMSI %s\n", gsup_msg->imsi); msub = msub_for_vsub(vsub); if (msub) LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Found already attached subscriber for IMSI %s\n", gsup_msg->imsi); if (vsub) { vlr_subscr_put(vsub, __func__); vsub = NULL; } /* Only for an incoming Handover Request: create a new remote-MSC-A as proxy for the MSC-A that is sending the * Handover Request */ if (!msub && gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) { msub = msc_new_msc_t_for_handover_request(net, gsup_msg); } if (!msub) { LOGP(DLGSUP, LOGL_ERROR, "%s: Cannot find subscriber for IMSI %s\n", __func__, osmo_quote_str(gsup_msg->imsi, -1)); return -EINVAL; } LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Rx GSUP %s\n", osmo_gsup_message_type_name(gsup_msg->message_type)); e = NULL; for (i = 0; i < ARRAY_SIZE(msub->role); i++) { msc_role = msub->role[i]; if (!msc_role) { LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "No %s\n", msc_role_name(i)); continue; } c = msc_role->priv; if (!c->remote_to) { LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has no remote\n", msc_role_name(i)); continue; } if (!e_link_matches_gsup_msg_source_name(c->remote_to, gsup_msg)) { LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has remote to mismatching %s\n", msc_role_name(i), c->remote_to->remote_name); continue; } /* Found a match. */ e = c->remote_to; break; } if (!e) { LOG_MSUB_CAT(msub, DLGSUP, LOGL_ERROR, "There is no E link that matches: Rx GSUP %s from %s\n", osmo_gsup_message_type_name(gsup_msg->message_type), osmo_quote_str((const char*)gsup_msg->source_name, gsup_msg->source_name_len)); return -EINVAL; } LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Rx GSUP %s from %s %s\n", osmo_gsup_message_type_name(gsup_msg->message_type), msc_role_name(c->role), e_link_name(e)); return osmo_fsm_inst_dispatch(msc_role, MSC_REMOTE_EV_RX_GSUP, (void*)gsup_msg); } void msc_a_i_t_gsup_init(struct gsm_network *net) { OSMO_ASSERT(net->gcm); OSMO_ASSERT(net->vlr); net->gcm->rx_cb[OSMO_GSUP_MESSAGE_CLASS_INTER_MSC] = (struct gsup_client_mux_rx_cb){ .func = msc_a_i_t_gsup_rx, .data = net, }; } int gsup_msg_assign_an_apdu(struct osmo_gsup_message *gsup_msg, struct an_apdu *an_apdu) { if (!an_apdu) { LOGP(DLGSUP, LOGL_ERROR, "Cannot assign NULL AN-APDU\n"); return -EINVAL; } gsup_msg->an_apdu = (struct osmo_gsup_an_apdu){ .access_network_proto = an_apdu->an_proto, }; if (an_apdu->msg) { gsup_msg->an_apdu.data = msgb_l2(an_apdu->msg); gsup_msg->an_apdu.data_len = msgb_l2len(an_apdu->msg); if (!gsup_msg->an_apdu.data || !gsup_msg->an_apdu.data_len) { LOGP(DLGSUP, LOGL_ERROR, "Cannot assign AN-APDU without msg->l2 to GSUP message: %s\n", msgb_hexdump(an_apdu->msg)); return -EINVAL; } } /* We are composing a struct osmo_gsup_msg from the osmo-msc internal struct an_apdu. The an_apdu may contain * additional info in form of a partly filled an_apdu->e_info. Make sure that data ends up in the resulting full * osmo_gsup_message. */ if (an_apdu->e_info) { const struct osmo_gsup_message *s = an_apdu->e_info; gsup_msg->msisdn_enc = s->msisdn_enc; gsup_msg->msisdn_enc_len = s->msisdn_enc_len; if (s->cause_rr_set) { gsup_msg->cause_rr = s->cause_rr; gsup_msg->cause_rr_set = true; } if (s->cause_bssap_set) { gsup_msg->cause_bssap = s->cause_bssap; gsup_msg->cause_bssap_set = true; } if (s->cause_sm) gsup_msg->cause_sm = s->cause_sm; } return 0; } /* Allocate a new msgb to contain the gsup_msg->an_apdu's data as l2h. * The msgb will have sufficient headroom to be passed down a RAN peer's SCCP user SAP. */ struct msgb *gsup_msg_to_msgb(const struct osmo_gsup_message *gsup_msg) { struct msgb *pdu; const uint8_t *pdu_data = gsup_msg->an_apdu.data; uint8_t pdu_len = gsup_msg->an_apdu.data_len; if (!pdu_data || !pdu_len) return NULL; /* Strictly speaking this is not limited to BSSMAP, but why not just use those sizes. */ pdu = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "AN-APDU from gsup_msg"); pdu->l2h = msgb_put(pdu, pdu_len); memcpy(pdu->l2h, pdu_data, pdu_len); return pdu; } /* 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 * msgb, so the returned an_apdu->msg must be freed if not NULL. */ void gsup_msg_to_an_apdu(struct an_apdu *an_apdu, const struct osmo_gsup_message *gsup_msg) { *an_apdu = (struct an_apdu){ .an_proto = gsup_msg->an_apdu.access_network_proto, .msg = gsup_msg_to_msgb(gsup_msg), .e_info = gsup_msg, }; }