diff options
Diffstat (limited to 'src/libmsc/msub.c')
-rw-r--r-- | src/libmsc/msub.c | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/src/libmsc/msub.c b/src/libmsc/msub.c new file mode 100644 index 000000000..fd8c7ea6e --- /dev/null +++ b/src/libmsc/msub.c @@ -0,0 +1,587 @@ +/* Manage all MSC roles of a connected subscriber (MSC-A, MSC-I, MSC-T) */ +/* + * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/msc/msub.h> +#include <osmocom/msc/msc_roles.h> +#include <osmocom/msc/msc_a.h> +#include <osmocom/msc/msc_i.h> +#include <osmocom/msc/msc_t.h> +#include <osmocom/msc/vlr.h> +#include <osmocom/msc/e_link.h> + +const struct value_string msc_role_names[] = { + { MSC_ROLE_A, "MSC-A" }, + { MSC_ROLE_I, "MSC-I" }, + { MSC_ROLE_T, "MSC-T" }, + {} +}; + +LLIST_HEAD(msub_list); + +#define for_each_msub_role(msub, role_idx) \ + for ((role_idx) = 0; (role_idx) < ARRAY_SIZE((msub)->role); (role_idx)++) \ + if ((msub)->role[role_idx]) + +enum msub_fsm_state { + MSUB_ST_ACTIVE, + MSUB_ST_TERMINATING, +}; + +enum msub_fsm_event { + MSUB_EV_ROLE_TERMINATED, +}; + +static void msub_check_for_release(struct osmo_fsm_inst *fi) +{ + struct msub *msub = fi->priv; + enum msc_role role_idx; + int role_present[MSC_ROLES_COUNT] = {}; + struct osmo_fsm_inst *child; + + /* See what child FSMs are still present. A caller might exchange roles by first allocating a new one as child + * of this FSM, and then exchanging the msub->role[] pointer. Even though the currently active role is removing + * itself from msub, we can still see whether another one is pending as a child of this msub. */ + llist_for_each_entry(child, &fi->proc.children, proc.child) { + struct msc_role_common *c = child->priv; + role_present[c->role]++; + } + + /* Log. */ + for (role_idx = 0; role_idx < ARRAY_SIZE(role_present); role_idx++) { + if (!role_present[role_idx]) + continue; + LOG_MSUB(msub, LOGL_DEBUG, "%d %s still active\n", role_present[role_idx], msc_role_name(role_idx)); + } + + /* To remain valid, there must be both an MSC-A role and one of MSC-I or MSC-T; + * except, SGs connections need no MSC-I or MSC-T. */ + if (role_present[MSC_ROLE_A] + && (role_present[MSC_ROLE_I] || role_present[MSC_ROLE_T] + || msub_msc_a(msub)->c.ran->type == OSMO_RAT_EUTRAN_SGS)) + return; + + /* The subscriber has become invalid. Go to terminating state to clearly signal that this msub is definitely + * going now. */ + osmo_fsm_inst_state_chg(fi, MSUB_ST_TERMINATING, 0, 0); +} + +void msub_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct msub *msub = fi->priv; + struct osmo_fsm_inst *role_fi; + + switch (event) { + case MSUB_EV_ROLE_TERMINATED: + role_fi = data; + /* Role implementations are required to pass their own osmo_fsm_inst pointer to osmo_fsm_inst_term(). */ + msub_remove_role(msub, role_fi); + msub_check_for_release(fi); + return; + default: + return; + } +} + +void msub_fsm_terminating_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +void msub_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct msub *msub = fi->priv; + LOG_MSUB(msub, LOGL_DEBUG, "Free\n"); + msub_set_vsub(msub, NULL); + llist_del(&msub->entry); +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state msub_fsm_states[] = { + [MSUB_ST_ACTIVE] = { + .name = "active", + .in_event_mask = S(MSUB_EV_ROLE_TERMINATED), + .out_state_mask = S(MSUB_ST_TERMINATING), + .action = msub_fsm_active, + }, + [MSUB_ST_TERMINATING] = { + .name = "terminating", + .onenter = msub_fsm_terminating_onenter, + }, +}; + +static const struct value_string msub_fsm_event_names[] = { + OSMO_VALUE_STRING(MSUB_EV_ROLE_TERMINATED), + {} +}; + +struct osmo_fsm msub_fsm = { + .name = "msub_fsm", + .states = msub_fsm_states, + .num_states = ARRAY_SIZE(msub_fsm_states), + .log_subsys = DMSC, + .event_names = msub_fsm_event_names, + .cleanup = msub_fsm_cleanup, +}; + +static __attribute__((constructor)) void msub_fsm_init() +{ + OSMO_ASSERT(osmo_fsm_register(&msub_fsm) == 0); +} + +struct msc_role_common *_msub_role_alloc(struct msub *msub, enum msc_role role, struct osmo_fsm *role_fsm, + size_t struct_size, const char *struct_name, struct ran_infra *ran) +{ + struct osmo_fsm_inst *fi; + struct msc_role_common *c; + + fi = osmo_fsm_inst_alloc_child(role_fsm, msub->fi, MSUB_EV_ROLE_TERMINATED); + OSMO_ASSERT(fi); + + c = (struct msc_role_common*)talloc_named_const(fi, struct_size, struct_name); + OSMO_ASSERT(c); + memset(c, 0, struct_size); + fi->priv = c; + + *c = (struct msc_role_common){ + .role = role, + .fi = fi, + .ran = ran, + }; + + msub_set_role(msub, fi); + return c; +} + +struct msub *msub_alloc(struct gsm_network *net) +{ + struct msub *msub; + struct osmo_fsm_inst *msub_fi = osmo_fsm_inst_alloc(&msub_fsm, net, NULL, LOGL_DEBUG, NULL); + OSMO_ASSERT(msub_fi); + + msub = talloc_zero(msub_fi, struct msub); + OSMO_ASSERT(msub); + msub_fi->priv = msub; + *msub = (struct msub){ + .net = net, + .fi = msub_fi, + }; + + llist_add_tail(&msub->entry, &msub_list); + return msub; +} + +/* Careful: the subscriber may not yet be authenticated, or may already be in release. Better use + * msc_a_for_vsub(for_vsub, true) to make sure you don't use an invalid conn. */ +struct msub *msub_for_vsub(const struct vlr_subscr *for_vsub) +{ + struct msub *msub; + if (!for_vsub) + return NULL; + + llist_for_each_entry(msub, &msub_list, entry) { + if (msub->vsub == for_vsub) + return msub; + } + + return NULL; +} + +const char *msub_name(const struct msub *msub) +{ + return vlr_subscr_name(msub? msub->vsub : NULL); +} + +void msub_set_role(struct msub *msub, struct osmo_fsm_inst *msc_role) +{ + struct osmo_fsm_inst *prev_role; + struct msc_role_common *c; + + OSMO_ASSERT(msc_role); + c = msc_role->priv; + + prev_role = msub->role[c->role]; + if (prev_role) + LOGPFSML(prev_role, LOGL_DEBUG, "Replaced by another %s\n", msc_role_name(c->role)); + + c->msub = msub; + msub->role[c->role] = msc_role; + msub_update_id(msub); + + if (prev_role) { + struct msc_role_common *prev_c = prev_role->priv; + switch (prev_c->role) { + case MSC_ROLE_I: + msc_i_clear(prev_role->priv); + break; + case MSC_ROLE_T: + msc_t_clear(prev_role->priv); + break; + default: + osmo_fsm_inst_term(prev_role, OSMO_FSM_TERM_REQUEST, prev_role); + break; + } + } +} + +void msub_remove_role(struct msub *msub, struct osmo_fsm_inst *fi) +{ + enum msc_role idx; + struct msc_role_common *c; + if (!msub || !fi) + return; + + c = fi->priv; + LOG_MSUB(msub, LOGL_DEBUG, "%s terminated\n", msc_role_name(c->role)); + + for_each_msub_role(msub, idx) { + if (msub->role[idx] == fi) + msub->role[idx] = NULL; + } +} + +struct msc_a *msub_msc_a(const struct msub *msub) +{ + struct osmo_fsm_inst *fi; + if (!msub) + return NULL; + fi = msub->role[MSC_ROLE_A]; + if (!fi) + return NULL; + return (struct msc_a*)fi->priv; +} + +struct msc_i *msub_msc_i(const struct msub *msub) +{ + struct osmo_fsm_inst *fi; + if (!msub) + return NULL; + fi = msub->role[MSC_ROLE_I]; + if (!fi) + return NULL; + return (struct msc_i*)fi->priv; +} + +struct msc_t *msub_msc_t(const struct msub *msub) +{ + struct osmo_fsm_inst *fi; + if (!msub) + return NULL; + fi = msub->role[MSC_ROLE_T]; + if (!fi) + return NULL; + return (struct msc_t*)fi->priv; +} + +/* Return the ran_conn of the MSC-I role, if available. If the MSC-I role is handled by a remote MSC, return NULL. */ +struct ran_conn *msub_ran_conn(const struct msub *msub) +{ + struct msc_i *msc_i = msub_msc_i(msub); + if (!msc_i) + return NULL; + return msc_i->ran_conn; +} + +static struct ran_infra *msub_ran(const struct msub *msub) +{ + int i; + struct msc_role_common *c; + + for (i = 0; i < MSC_ROLES_COUNT; i++) { + if (!msub->role[i]) + continue; + c = msub->role[i]->priv; + if (!c->ran) + continue; + return c->ran; + } + + return &msc_ran_infra[OSMO_RAT_UNKNOWN]; +} + +const char *msub_ran_conn_name(const struct msub *msub) +{ + struct msc_i *msc_i = msub_msc_i(msub); + struct msc_t *msc_t = msub_msc_t(msub); + if (msc_i && msc_i->c.remote_to) + return e_link_name(msc_i->c.remote_to); + if (msc_i && msc_i->ran_conn) + return ran_conn_name(msc_i->ran_conn); + if (msc_t && msc_t->c.remote_to) + return e_link_name(msc_t->c.remote_to); + if (msc_t && msc_t->ran_conn) + return ran_conn_name(msc_t->ran_conn); + return osmo_rat_type_name(msub_ran(msub)->type); +} + +int msub_set_vsub(struct msub *msub, struct vlr_subscr *vsub) +{ + OSMO_ASSERT(msub); + if (msub->vsub == vsub) + return 0; + if (msub->vsub && vsub) { + LOG_MSUB(msub, LOGL_ERROR, + "Changing a connection's VLR Subscriber is not allowed: not changing to %s\n", + vlr_subscr_name(vsub)); + return -ENOTSUP; + } + if (vsub) { + struct msub *other_msub = msub_for_vsub(vsub); + if (other_msub) { + struct msc_a *msc_a = msub_msc_a(msub); + struct msc_a *other_msc_a = msub_msc_a(other_msub); + LOG_MSC_A(msc_a, LOGL_ERROR, + "Cannot associate with VLR subscr, another connection is already active%s%s\n", + other_msc_a ? " at " : "", other_msc_a ? other_msc_a->c.fi->id : ""); + LOG_MSC_A(other_msc_a, LOGL_ERROR, "Attempt to associate a second subscriber connection%s%s\n", + msc_a ? " at " : "", msc_a ? msc_a->c.fi->id : ""); + if (other_msc_a && msc_a_in_release(other_msc_a)) { + LOG_MSC_A(other_msc_a, LOGL_ERROR, + "Another connection for this subscriber is coming up, since this" + " is already in release, forcefully discarding it\n"); + osmo_fsm_inst_term(other_msc_a->c.fi, OSMO_FSM_TERM_ERROR, other_msc_a->c.fi); + /* Count this as "recovered from duplicate connection" error and do associate. */ + } else + return -EINVAL; + } + } + if (msub->vsub) { + vlr_subscr_put(msub->vsub, VSUB_USE_MSUB); + msub->vsub = NULL; + } + if (vsub) { + vlr_subscr_get(vsub, VSUB_USE_MSUB); + msub->vsub = vsub; + vsub->cs.attached_via_ran = msub_ran(msub)->type; + msub_update_id(msub); + } + return 0; +} + +struct vlr_subscr *msub_vsub(const struct msub *msub) +{ + return msub ? msub->vsub : NULL; +} + +struct gsm_network *msub_net(const struct msub *msub) +{ + OSMO_ASSERT(msub->net); + return msub->net; +} + +int msub_role_to_role_event(struct msub *msub, enum msc_role from_role, enum msc_role to_role) +{ + switch (from_role) { + case MSC_ROLE_A: + switch (to_role) { + case MSC_ROLE_I: + return MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST; + case MSC_ROLE_T: + return MSC_T_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST; + default: + break; + } + break; + + case MSC_ROLE_I: + switch (to_role) { + case MSC_ROLE_A: + return MSC_A_EV_FROM_I_PROCESS_ACCESS_SIGNALLING_REQUEST; + default: + break; + } + break; + + case MSC_ROLE_T: + switch (to_role) { + case MSC_ROLE_A: + return MSC_A_EV_FROM_T_PROCESS_ACCESS_SIGNALLING_REQUEST; + default: + break; + } + break; + + default: + break; + } + + LOG_MSUB(msub, LOGL_ERROR, "Cannot tx DTAP from %s to %s\n", msc_role_name(from_role), msc_role_name(to_role)); + return -1; +} + +/* The caller retains ownership of the an_apdu_msg -- don't forget to msgb_free() it. */ +int _msub_role_dispatch(struct msub *msub, enum msc_role to_role, uint32_t to_role_event, const struct an_apdu *an_apdu, + const char *file, int line) +{ + struct osmo_fsm_inst *to_fi = msub->role[to_role]; + + if (!to_fi) { + LOG_MSUB_CAT_SRC(msub, DMSC, LOGL_ERROR, file, line, + "Cannot tx event to %s, no such role defined\n", msc_role_name(to_role)); + return -EINVAL; + } + + return _osmo_fsm_inst_dispatch(to_fi, to_role_event, (void*)an_apdu, file, line); +} + +/* The caller retains ownership of the an_apdu_msg -- don't forget to msgb_free() it. */ +int msub_tx_an_apdu(struct msub *msub, enum msc_role from_role, enum msc_role to_role, struct an_apdu *an_apdu) +{ + int event = msub_role_to_role_event(msub, from_role, to_role); + if (event < 0) + return event; + return msub_role_dispatch(msub, to_role, event, an_apdu); +} + +static void _msub_update_id(struct msub *msub, const char *subscr_name) +{ + enum msc_role idx; + struct msc_a *msc_a = msub_msc_a(msub); + struct vlr_subscr *vsub = msub_vsub(msub); + const char *compl_l3_name = NULL; + char id[128]; + + if (msc_a) + compl_l3_name = get_value_string_or_null(complete_layer3_type_names, msc_a->complete_layer3_type); + if (!compl_l3_name) + compl_l3_name = "no-compl-l3"; + + snprintf(id, sizeof(id), "%s:%s:%s", subscr_name, msub_ran_conn_name(msub), compl_l3_name); + osmo_identifier_sanitize_buf(id, NULL, '-'); + + for_each_msub_role(msub, idx) { + osmo_fsm_inst_update_id(msub->role[idx], id); + } + if (vsub) { + if (vsub->lu_fsm) + osmo_fsm_inst_update_id(vsub->lu_fsm, id); + if (vsub->auth_fsm) + osmo_fsm_inst_update_id(vsub->auth_fsm, id); + if (vsub->proc_arq_fsm) + osmo_fsm_inst_update_id(vsub->proc_arq_fsm, id); + } +} + +/* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */ +void msub_update_id_from_mi(struct msub *msub, const uint8_t mi[], uint8_t mi_len) +{ + _msub_update_id(msub, osmo_mi_name(mi, mi_len)); +} + +/* Update msub->fi id string from current msub->vsub and msub->complete_layer3_type. */ +void msub_update_id(struct msub *msub) +{ + if (!msub) + return; + _msub_update_id(msub, vlr_subscr_name(msub->vsub)); +} + +/* Iterate all msub instances that are relevant for this subscriber, and update FSM ID strings for all of the FSM + * instances. */ +void msub_update_id_for_vsub(struct vlr_subscr *for_vsub) +{ + struct msub *msub; + if (!for_vsub) + return; + + llist_for_each_entry(msub, &msub_list, entry) { + if (msub->vsub == for_vsub) + msub_update_id(msub); + } +} + +void msc_role_forget_conn(struct osmo_fsm_inst *role, struct ran_conn *conn) +{ + struct msc_i *old_i = role->priv; + struct msc_t *old_t = role->priv; + struct msc_role_common *c = role->priv; + struct ran_conn **conn_p = NULL; + + switch (c->role) { + case MSC_ROLE_I: + conn_p = &old_i->ran_conn; + break; + + case MSC_ROLE_T: + conn_p = &old_t->ran_conn; + break; + default: + break; + } + + if (!conn_p) + return; + + if (*conn_p != conn) + return; + + (*conn_p)->msc_role = NULL; + *conn_p = NULL; +} + +struct msgb *msc_role_ran_encode(struct osmo_fsm_inst *fi, const struct ran_msg *ran_msg) +{ + struct msc_role_common *c = fi->priv; + struct msgb *msg; + if (!c->ran->ran_encode) { + LOGPFSML(fi, LOGL_ERROR, "Cannot encode %s: no NAS encoding function defined for RAN type %s\n", + ran_msg_type_name(ran_msg->msg_type), osmo_rat_type_name(c->ran->type)); + return NULL; + } + msg = c->ran->ran_encode(fi, ran_msg); + if (!msg) + LOGPFSML(fi, LOGL_ERROR, "Failed to encode %s\n", ran_msg_type_name(ran_msg->msg_type)); + return msg; +} + +int msc_role_ran_decode(struct osmo_fsm_inst *fi, const struct an_apdu *an_apdu, + ran_decode_cb_t decode_cb, void *decode_cb_data) +{ + struct ran_dec ran_dec; + struct msc_role_common *c = fi->priv; + if (!an_apdu) { + LOGPFSML(fi, LOGL_ERROR, "NULL AN-APDU\n"); + return -EINVAL; + } + if (an_apdu->an_proto != c->ran->an_proto) { + LOGPFSML(fi, LOGL_ERROR, "Unexpected AN-APDU protocol: %s\n", an_proto_name(an_apdu->an_proto)); + return -EINVAL; + } + if (!an_apdu->msg) { + LOGPFSML(fi, LOGL_DEBUG, "No PDU in this AN-APDU\n"); + return 0; + } + ran_dec = (struct ran_dec) { + .caller_fi = fi, + .caller_data = decode_cb_data, + .decode_cb = decode_cb, + }; + if (!c->ran->ran_dec_l2) { + LOGPFSML(fi, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n", + osmo_rat_type_name(c->ran->type)); + return -ENOTSUP; + } + return c->ran->ran_dec_l2(&ran_dec, an_apdu->msg); +} |