/* RUA side FSM of hnbgw_context_map */ /* (C) 2023 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 "config.h" #include #include #include #if ENABLE_PFCP #include #endif #include #include #include #include #include #include #include enum map_rua_fsm_state { MAP_RUA_ST_INIT, MAP_RUA_ST_CONNECTED, MAP_RUA_ST_DISCONNECTED, MAP_RUA_ST_DISRUPTED, }; static const struct value_string map_rua_fsm_event_names[] = { OSMO_VALUE_STRING(MAP_RUA_EV_RX_CONNECT), OSMO_VALUE_STRING(MAP_RUA_EV_RX_DIRECT_TRANSFER), OSMO_VALUE_STRING(MAP_RUA_EV_RX_DISCONNECT), OSMO_VALUE_STRING(MAP_RUA_EV_TX_DIRECT_TRANSFER), OSMO_VALUE_STRING(MAP_RUA_EV_CN_DISC), OSMO_VALUE_STRING(MAP_RUA_EV_HNB_LINK_LOST), {} }; static struct osmo_fsm map_rua_fsm; static const struct osmo_tdef_state_timeout map_rua_fsm_timeouts[32] = { [MAP_RUA_ST_INIT] = { .T = -31 }, [MAP_RUA_ST_DISCONNECTED] = { .T = -31 }, [MAP_RUA_ST_DISRUPTED] = { .T = -31 }, }; /* Transition to a state, using the T timer defined in map_rua_fsm_timeouts. * Assumes local variable fi exists. */ #define map_rua_fsm_state_chg(state) \ OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \ map_rua_fsm_timeouts, \ hnbgw_T_defs, \ 5) == 0) void map_rua_fsm_alloc(struct hnbgw_context_map *map) { struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_rua_fsm, map, map, LOGL_DEBUG, NULL); OSMO_ASSERT(fi); osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-%s-RUA-%u", hnb_context_name(map->hnb_ctx), map->is_ps ? "PS" : "CS", map->rua_ctx_id); OSMO_ASSERT(map->rua_fi == NULL); map->rua_fi = fi; /* trigger the timeout */ map_rua_fsm_state_chg(MAP_RUA_ST_INIT); } enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map) { if (!map || !map->rua_fi) return MAP_S_DISCONNECTING; switch (map->rua_fi->state) { case MAP_RUA_ST_INIT: return MAP_S_CONNECTING; case MAP_RUA_ST_CONNECTED: return MAP_S_ACTIVE; default: case MAP_RUA_ST_DISCONNECTED: case MAP_RUA_ST_DISRUPTED: return MAP_S_DISCONNECTING; } } bool map_rua_is_active(struct hnbgw_context_map *map) { if (!map || !map->rua_fi) return false; switch (map->rua_fi->state) { case MAP_RUA_ST_DISCONNECTED: case MAP_RUA_ST_DISRUPTED: return false; default: return true; } } static int map_rua_fsm_timer_cb(struct osmo_fsm_inst *fi) { /* Return 1 to terminate FSM instance, 0 to keep running */ switch (fi->state) { default: map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED); return 0; case MAP_RUA_ST_DISCONNECTED: case MAP_RUA_ST_DISRUPTED: return 1; } } static int destruct_ranap_cn_rx_co_ies(ranap_message *ranap_message_p) { ranap_cn_rx_co_free(ranap_message_p); return 0; } /* Decode RANAP message with convenient memory freeing: just talloc_free() the returned pointer.. * Allocate a ranap_message from OTC_SELECT, decode RANAP msgb into it, attach a talloc destructor that calls * ranap_cn_rx_co_free() upon talloc_free(), and return the decoded ranap_message. */ ranap_message *hnbgw_decode_ranap_co(struct msgb *ranap_msg) { int rc; ranap_message *message; if (!msg_has_l2_data(ranap_msg)) return NULL; message = talloc_zero(OTC_SELECT, ranap_message); rc = ranap_cn_rx_co_decode2(message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg)); if (rc != 0) { talloc_free(message); return NULL; } talloc_set_destructor(message, destruct_ranap_cn_rx_co_ies); return message; } /* Dispatch RANAP message to SCCP, if any. */ static int handle_rx_rua(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) { struct hnbgw_context_map *map = fi->priv; /* If the FSM instance has already terminated, don't dispatch anything. */ if (fi->proc.terminating) return 0; if (!msg_has_l2_data(ranap_msg)) return 0; ranap_message *message = hnbgw_decode_ranap_co(ranap_msg); if (message) { LOGPFSML(fi, LOGL_DEBUG, "rx from RUA: RANAP %s\n", get_value_string(ranap_procedure_code_vals, message->procedureCode)); kpi_ranap_process_ul(map, message); if (!map->is_ps) { /* See if it is a RAB Assignment Response message from RUA to SCCP, where we need to change the user plane * information, for RTP mapping via MGW, or GTP mapping via UPF. */ switch (message->procedureCode) { case RANAP_ProcedureCode_id_RAB_Assignment: /* mgw_fsm_handle_rab_ass_resp() takes ownership of prim->oph and (ranap) message */ return mgw_fsm_handle_cs_rab_ass_resp(map, ranap_msg, message); } } else { #if ENABLE_PFCP if (hnb_gw_is_gtp_mapping_enabled()) { /* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */ switch (message->procedureCode) { case RANAP_ProcedureCode_id_RAB_Assignment: /* ps_rab_ass_fsm takes ownership of prim->oph and RANAP message */ return hnbgw_gtpmap_rx_rab_ass_resp(map, ranap_msg, message); } } #endif } } /* It was not a RAB Assignment Response that needed to be intercepted. Forward as-is to SCCP. */ return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg); } static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg) { int rc; if (!msg_has_l2_data(ranap_msg)) return 0; if (!map->hnb_ctx) { LOGPFSML(map->rua_fi, LOGL_ERROR, "Cannot transmit RUA DirectTransfer: HNB has disconnected\n"); return -ENOTCONN; } rc = rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msgb_l2(ranap_msg), msgb_l2len(ranap_msg)); if (rc) LOGPFSML(map->rua_fi, LOGL_ERROR, "Failed to transmit RUA DirectTransfer to HNB\n"); return rc; } static void map_rua_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct msgb *ranap_msg = data; switch (event) { case MAP_RUA_EV_RX_CONNECT: /* not needed for RAB assignment scanning, but for KPI scanning */ handle_rx_rua(fi, ranap_msg); map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED); return; case MAP_RUA_EV_RX_DISCONNECT: /* Unlikely that SCCP is active, but let the SCCP FSM decide about that. */ handle_rx_rua(fi, ranap_msg); /* There is a reason to shut down this RUA connection. Super unlikely, we haven't even processed the * MAP_RUA_EV_RX_CONNECT that created this FSM. Semantically, RUA is not connected, so we can * directly go to MAP_RUA_ST_DISCONNECTED. */ map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED); break; case MAP_RUA_EV_CN_DISC: case MAP_RUA_EV_HNB_LINK_LOST: map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED); break; default: OSMO_ASSERT(false); } } static void map_rua_tx_disconnect(struct osmo_fsm_inst *fi) { struct hnbgw_context_map *map = fi->priv; RUA_Cause_t rua_cause; if (!map->hnb_ctx || !map->hnb_ctx->conn) { /* HNB already disconnected, nothing to do. */ LOGPFSML(fi, LOGL_NOTICE, "HNB vanished, this RUA context cannot disconnect gracefully\n"); return; } /* Send Disconnect to RUA without RANAP data. */ rua_cause = (RUA_Cause_t){ .present = RUA_Cause_PR_radioNetwork, .choice.radioNetwork = RUA_CauseRadioNetwork_network_release, }; LOGPFSML(fi, LOGL_INFO, "Tx RUA Disconnect\n"); if (rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id, &rua_cause, NULL, 0)) LOGPFSML(fi, LOGL_ERROR, "Failed to send Disconnect to RUA\n"); } static void map_rua_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct hnbgw_context_map *map = fi->priv; struct msgb *ranap_msg = data; switch (event) { case MAP_RUA_EV_RX_DIRECT_TRANSFER: /* received DirectTransfer from RUA, forward to SCCP */ handle_rx_rua(fi, ranap_msg); return; case MAP_RUA_EV_TX_DIRECT_TRANSFER: /* Someone (usually the SCCP side) wants us to send a RANAP payload to HNB via RUA */ forward_ranap_to_rua(map, ranap_msg); return; case MAP_RUA_EV_RX_DISCONNECT: /* received Disconnect from RUA. forward any payload to SCCP, and change state. */ if (!map_sccp_is_active(map)) { /* If, unlikely, the SCCP is already gone, changing to MAP_RUA_ST_DISCONNECTED frees the * hnbgw_context_map. Avoid a use-after-free. */ map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED); return; } map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED); handle_rx_rua(fi, ranap_msg); return; case MAP_RUA_EV_HNB_LINK_LOST: /* The HNB is gone. Cannot gracefully cleanup the RUA connection, just be gone. */ map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED); return; case MAP_RUA_EV_CN_DISC: /* There is a disruptive reason to shut down this RUA connection, HNB is still there */ OSMO_ASSERT(data == NULL); map_rua_tx_disconnect(fi); map_rua_fsm_state_chg(MAP_RUA_ST_DISRUPTED); return; default: OSMO_ASSERT(false); } } static void map_rua_free_if_done(struct hnbgw_context_map *map, uint32_t sccp_event) { /* From RUA's POV, we can now free the hnbgw_context_map. * If SCCP is still active, tell it to disconnect -- in that case the SCCP side will call context_map_free(). * If SCCP is no longer active, free this map. */ if (map_sccp_is_active(map)) map_sccp_dispatch(map, sccp_event, NULL); else context_map_free(map); } static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct hnbgw_context_map *map = fi->priv; map_rua_free_if_done(map, MAP_SCCP_EV_RAN_DISC); } static void map_rua_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct msgb *ranap_msg = data; if (msg_has_l2_data(ranap_msg)) LOGPFSML(fi, LOGL_ERROR, "RUA not connected, cannot dispatch RANAP message\n"); /* Ignore all events. */ } static void map_rua_disrupted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct hnbgw_context_map *map = fi->priv; map_rua_free_if_done(map, MAP_SCCP_EV_RAN_LINK_LOST); } void map_rua_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct hnbgw_context_map *map = fi->priv; map->rua_fi = NULL; } #define S(x) (1 << (x)) static const struct osmo_fsm_state map_rua_fsm_states[] = { [MAP_RUA_ST_INIT] = { .name = "init", .in_event_mask = 0 | S(MAP_RUA_EV_RX_CONNECT) | S(MAP_RUA_EV_RX_DISCONNECT) | S(MAP_RUA_EV_CN_DISC) | S(MAP_RUA_EV_HNB_LINK_LOST) , .out_state_mask = 0 | S(MAP_RUA_ST_INIT) | S(MAP_RUA_ST_CONNECTED) | S(MAP_RUA_ST_DISCONNECTED) | S(MAP_RUA_ST_DISRUPTED) , .action = map_rua_init_action, }, [MAP_RUA_ST_CONNECTED] = { .name = "connected", .in_event_mask = 0 | S(MAP_RUA_EV_RX_DIRECT_TRANSFER) | S(MAP_RUA_EV_TX_DIRECT_TRANSFER) | S(MAP_RUA_EV_RX_DISCONNECT) | S(MAP_RUA_EV_CN_DISC) | S(MAP_RUA_EV_HNB_LINK_LOST) , .out_state_mask = 0 | S(MAP_RUA_ST_DISCONNECTED) | S(MAP_RUA_ST_DISRUPTED) , .action = map_rua_connected_action, }, [MAP_RUA_ST_DISCONNECTED] = { .name = "disconnected", .in_event_mask = 0 | S(MAP_RUA_EV_CN_DISC) | S(MAP_RUA_EV_HNB_LINK_LOST) , .onenter = map_rua_disconnected_onenter, .action = map_rua_disconnected_action, }, [MAP_RUA_ST_DISRUPTED] = { .name = "disrupted", .in_event_mask = 0 | S(MAP_RUA_EV_CN_DISC) | S(MAP_RUA_EV_HNB_LINK_LOST) , .onenter = map_rua_disrupted_onenter, /* same as MAP_RUA_ST_DISCONNECTED: */ .action = map_rua_disconnected_action, }, }; static struct osmo_fsm map_rua_fsm = { .name = "map_rua", .states = map_rua_fsm_states, .num_states = ARRAY_SIZE(map_rua_fsm_states), .log_subsys = DHNB, .event_names = map_rua_fsm_event_names, .timer_cb = map_rua_fsm_timer_cb, .cleanup = map_rua_fsm_cleanup, }; static __attribute__((constructor)) void map_rua_fsm_register(void) { OSMO_ASSERT(osmo_fsm_register(&map_rua_fsm) == 0); }