From ed424d5be471376d482d203c2e78deb1f836c119 Mon Sep 17 00:00:00 2001 From: Neels Janosch Hofmeyr Date: Sun, 12 Feb 2023 05:02:48 +0100 Subject: [PATCH] context map: introduce RUA and SCCP FSMs to fix leaks Refactor the entire RUA <-> SCCP connection-oriented message forwarding: - conquer confusion about hnbgw_context_map release behavior, and - eradicate SCCP connection leaks. Finer points: == Context map state == So far, we had a single context map state and some flags to keep track of both the RUA and the SCCP connections. It was easy to miss connection cleanup steps, especially on the SCCP side. Instead, the two FSMs clearly define the RUA and SCCP conn states separately, and each side takes care of its own release needs for all possible scenarios. - When both RUA and SCCP are released, the context map is discarded. - A context map can stay around to wait for proper SCCP release, even if the RUA side has lost the HNB connection. - Completely drop the async "context mapper garbage collection", because the FSMs clarify the release and free steps, synchronously. - We still keep a (simplified) enum for global context map state, but this is only used so that VTY reporting remains mostly unchanged. == Context map cleanup confusion == The function context_map_hnb_released() was the general cleanup function for a context map. Instead, add separate context_map_free(). == Free context maps separately from HNB == When a HNB releases, talloc_steal() the context maps out of the HNB specific hnb_ctx, so that they are not freed along with the HNB state, possibly leaving SCCP connections afloat. (It is still nice to normally keep context maps as talloc children of their respective hnb_ctx, so talloc reports show which belongs to which.) So far, context map handling found the global hnb_gw pointer via map->hnb_ctx->gw. But in fact, a HNB may disappear at any point in time. Instead, use a separate hnb_gw pointer in map->gw. == RUA procedure codes vs. SCCP prims == So far, the RUA rx side composed SCCP prims to pass on: RUA rx ---SCCP-prim--> RANAP handling ---SCCP-prim--> SCCP tx That is a source of confusion: a RUA procedure code should not translate 1:1 to SCCP prims, especially for RUA id-Disconnect (see release charts below). Instead, move SCCP prim composition over to the SCCP side, using FSM events to forward: RUA rx --event--> RUA FSM --event--> SCCP FSM --SCCP-prim--> SCCP tx +RANAP +RANAP +RANAP RUA tx <--RUA---- RUA FSM <--event-- SCCP FSM <--event-- SCCP rx +RANAP +RANAP +RANAP Hence choose the correct prim according to the SCCP FSM state. - in hnbgw_rua.c, use RUA procedure codes, not prim types. - via the new FSM events' data args, pass msgb containing RANAP PDUs. == Fix SCCP Release behavior == So far, the normal conn release behavior was HNB HNBGW CN | --id-Disconnect--> | ---SCCP-Released--> | Iu-ReleaseComplete | | <--SCCP-RLC-------- | (no data) Instead, the SCCP release is now in accordance with 3GPP TS 48.006 9.2 'Connection release': The MSC sends a SCCP released message. This message shall not contain any user data field. i.e.: HNB HNBGW CN | --id-Disconnect--> | ---Data-Form-1(!)--> | Iu-ReleaseComplete | | <--SCCP-Released---- | (no data) | | ---SCCP-RLC--------> | (no data) (Side note, the final SCCP Release Confirm step is taken care of implicitly by libosmo-sigtran's sccp_scoc.c FSM.) If the CN fails to respond with SCCP-Released, on new X31 timeout, osmo-hnbgw will send an SCCP Released to the CN as fallback. == Memory model for message dispatch == So far, an osmo_scu_prim aka "oph" was passed between RUA and SCCP handling code, and the final dispatch freed it. Every error path had to take care not to leak any oph. Instead, use a much easier and much more leakage proof memory model, inspired by fixeria: - on rx, dispatch RANAP msgb that live in OTC_SELECT. - no code path needs to msgb_free() -- the msgb is discarded via OTC_SELECT when handling is done, error or no error. - any code path may also choose to store the msgb for async dispatch, using talloc_steal(). The user plane mapping via MGW and UPF do that. - if any code path does msgb_free(), that would be no problem either (but none do so now, for simplicity). == Layer separation == Dispatch *all* connection-oriented RUA tx via the RUA FSM and SCCP tx via the SCCP FSM, do not call rua_tx_dt() or osmo_sccp_user_sap_down() directly. == Memory model for decoded ranap_message IEs == Use a talloc destructor to make sure that the ranap_message IEs are always implicitly freed upon talloc_free(), so that no code path can possibly forget to do so. == Implicit cleanup by talloc == Use talloc scoping to remove a bunch of explicit cleanup code. For example, make a chached message a talloc child of its handler: talloc_steal(mgw_fsm_priv, message); mgw_fsm_priv->ranap_rab_ass_req_message = message; and later implicitly free 'message' by only freeing the handler: talloc_free(mgw_fsm_priv) Related: SYS#6297 Change-Id: I6ff7e36532ff57c6f2d3e7e419dd22ef27dafd19 --- include/osmocom/hnbgw/context_map.h | 103 ++++- include/osmocom/hnbgw/hnbgw.h | 4 + include/osmocom/hnbgw/hnbgw_cn.h | 2 + include/osmocom/hnbgw/mgw_fsm.h | 4 +- include/osmocom/hnbgw/ps_rab_ass_fsm.h | 4 +- include/osmocom/hnbgw/tdefs.h | 1 + src/osmo-hnbgw/Makefile.am | 2 + src/osmo-hnbgw/context_map.c | 161 +++++--- src/osmo-hnbgw/context_map_rua.c | 361 ++++++++++++++++ src/osmo-hnbgw/context_map_sccp.c | 546 +++++++++++++++++++++++++ src/osmo-hnbgw/hnbgw.c | 37 +- src/osmo-hnbgw/hnbgw_cn.c | 102 +---- src/osmo-hnbgw/hnbgw_rua.c | 102 +---- src/osmo-hnbgw/hnbgw_vty.c | 6 +- src/osmo-hnbgw/mgw_fsm.c | 135 +++--- src/osmo-hnbgw/ps_rab_ass_fsm.c | 65 ++- src/osmo-hnbgw/ps_rab_fsm.c | 5 +- src/osmo-hnbgw/tdefs.c | 6 + 18 files changed, 1271 insertions(+), 375 deletions(-) create mode 100644 src/osmo-hnbgw/context_map_rua.c create mode 100644 src/osmo-hnbgw/context_map_sccp.c diff --git a/include/osmocom/hnbgw/context_map.h b/include/osmocom/hnbgw/context_map.h index 7bcdf57..b64358a 100644 --- a/include/osmocom/hnbgw/context_map.h +++ b/include/osmocom/hnbgw/context_map.h @@ -2,6 +2,7 @@ #include #include +#include #define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \ LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \ @@ -10,12 +11,56 @@ (HNB_CTX_MAP) ? ((HNB_CTX_MAP)->is_ps ? "PS" : "CS") : "NULL", \ ##ARGS) +/* All these events' data argument may either be NULL, or point to a RANAP msgb. + * - The msgb shall be in the OTC_SELECT talloc pool, so that they will be deallocated automatically. Some events + * processing will store the msgb for later, in which case it will take over ownership of the msgb by means of + * talloc_steal(). + * - For events that may send a RANAP message towards CN via SCCP, the msgb shall have reserved headroom to fit a struct + * osmo_scu_prim. These are: MAP_RUA_EV_RX_*. + * - The RANAP message shall be at msgb_l2(). + */ +enum map_rua_fsm_event { + /* Receiving a RUA Connect from HNB. */ + MAP_RUA_EV_RX_CONNECT, + /* Receiving some data from HNB via RUA, to forward via SCCP to CN. */ + MAP_RUA_EV_RX_DIRECT_TRANSFER, + /* Receiving a RUA Disconnect from HNB. */ + MAP_RUA_EV_RX_DISCONNECT, + /* SCCP has received some data from CN to forward via RUA to HNB. */ + MAP_RUA_EV_TX_DIRECT_TRANSFER, + /* The CN side is disconnected (e.g. received an SCCP Released), that means we are going gracefully disconnect + * RUA, too. */ + MAP_RUA_EV_CN_DISC, + /* All of a sudden, there is no RUA link. For example, HNB vanished / restarted, or SCTP SHUTDOWN on the RUA + * link. Skip RUA disconnect. */ + MAP_RUA_EV_HNB_LINK_LOST, +}; + +/* All these events' data argument is identical to enum map_rua_fsm_event, with this specialisation: + * - The events that may send a RANAP message towards CN via SCCP and hence require a headroom for an osmo_scu_prim are: + * MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_RAN_DISC. + */ +enum map_sccp_fsm_event { + /* Receiving an SCCP CC from CN. */ + MAP_SCCP_EV_RX_CONNECTION_CONFIRM, + /* Receiving some data from CN via SCCP, to forward via RUA to HNB. */ + MAP_SCCP_EV_RX_DATA_INDICATION, + /* RUA has received some data from HNB to forward via SCCP to CN. */ + MAP_SCCP_EV_TX_DATA_REQUEST, + /* The RAN side received a Disconnect, that means we are going to expect SCCP to disconnect too. + * CN should have received an Iu-ReleaseComplete with or before this, give CN a chance to send an SCCP RLSD; + * after a timeout we will send a non-standard RLSD to the CN instead. */ + MAP_SCCP_EV_RAN_DISC, + /* Receiving an SCCP RLSD from CN, or libosmo-sigtran tells us about SCCP connection timeout. All done. */ + MAP_SCCP_EV_RX_RELEASED, +}; + +/* For context_map_get_state(), to combine the RUA and SCCP states, for VTY reporting only. */ enum hnbgw_context_map_state { - MAP_S_NULL, - MAP_S_ACTIVE, /* currently active map */ - MAP_S_RESERVED1, /* just disconnected, still resrved */ - MAP_S_RESERVED2, /* still reserved */ - MAP_S_NUM_STATES /* Number of states, keep this at the end */ + MAP_S_CONNECTING, /* not active yet; effectively waiting for SCCP CC */ + MAP_S_ACTIVE, /* both RUA and SCCP are connected */ + MAP_S_DISCONNECTING, /* not active anymore; effectively waiting for SCCP RLSD */ + MAP_S_NUM_STATES /* Number of states, keep this at the end */ }; extern const struct value_string hnbgw_context_map_state_names[]; @@ -28,27 +73,36 @@ struct hnbgw_cnlink; struct hnbgw_context_map { /* entry in the per-CN list of mappings */ struct llist_head cn_list; - /* entry in the per-HNB list of mappings. */ + /* entry in the per-HNB list of mappings. If hnb_ctx == NULL, then this llist entry has been llist_del()eted and + * must not be used. */ struct llist_head hnb_list; - /* Pointer to HNB for this map, to transceive RUA. */ + /* Backpointer to global hnb_gw. */ + struct hnb_gw *gw; + + /* Pointer to HNB for this map, to transceive RUA. If the HNB has disconnected without releasing the RUA + * context, this is NULL. */ struct hnb_context *hnb_ctx; /* RUA context ID used in RUA messages to/from the hnb_gw. */ uint32_t rua_ctx_id; + /* FSM handling the RUA state for rua_ctx_id. */ + struct osmo_fsm_inst *rua_fi; /* Pointer to CN, to transceive SCCP. */ struct hnbgw_cnlink *cn_link; /* SCCP User SAP connection ID used in SCCP messages to/from the cn_link. */ uint32_t scu_conn_id; - /* Set to true on SCCP Conn Conf, set to false when an OSMO_SCU_PRIM_N_DISCONNECT has been sent for the SCCP - * User SAP conn. Useful to avoid leaking SCCP connections: guarantee that an OSMO_SCU_PRIM_N_DISCONNECT gets - * sent, even when RUA fails to gracefully disconnect. */ - bool scu_conn_active; + /* FSM handling the SCCP state for scu_conn_id. */ + struct osmo_fsm_inst *sccp_fi; /* False for CS, true for PS */ bool is_ps; - enum hnbgw_context_map_state state; + /* When an FSM is asked to disconnect but must still wait for a response, it may set this flag, to continue to + * disconnect once the response is in. In particular, when SCCP is asked to disconnect after an SCCP Connection + * Request was already sent and while waiting for a Connection Confirmed, we should still wait for the SCCP CC + * and immediately release it after that, to not leak the connection. */ + bool please_disconnect; /* FSM instance for the MGW, handles the async MGCP communication necessary to intercept CS RAB Assignment and * redirect the RTP via the MGW. */ @@ -71,17 +125,40 @@ struct hnbgw_context_map { /* All PS RABs and their GTP tunnel mappings. list of struct ps_rab. Each ps_rab FSM handles the PFCP * communication for one particular RAB ID. */ struct llist_head ps_rabs; + + /* Flag to prevent calling context_map_free() from cleanup code paths triggered by context_map_free() itself. */ + bool deallocating; }; +enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map); +enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map); +enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map); struct hnbgw_context_map * context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, bool is_ps, struct hnbgw_cnlink *cn_if_new); +void map_rua_fsm_alloc(struct hnbgw_context_map *map); +void map_sccp_fsm_alloc(struct hnbgw_context_map *map); + struct hnbgw_context_map * context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id); void context_map_hnb_released(struct hnbgw_context_map *map); -int context_map_init(struct hnb_gw *gw); +#define map_rua_dispatch(MAP, EVENT, MSGB) \ + _map_rua_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__) +int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg, + const char *file, int line); + +#define map_sccp_dispatch(MAP, EVENT, MSGB) \ + _map_sccp_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__) +int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg, + const char *file, int line); + +bool map_rua_is_active(struct hnbgw_context_map *map); +bool map_sccp_is_active(struct hnbgw_context_map *map); +void context_map_check_released(struct hnbgw_context_map *map); + +unsigned int msg_has_l2_data(const struct msgb *msg); diff --git a/include/osmocom/hnbgw/hnbgw.h b/include/osmocom/hnbgw/hnbgw.h index f1b107e..96a2ef3 100644 --- a/include/osmocom/hnbgw/hnbgw.h +++ b/include/osmocom/hnbgw/hnbgw.h @@ -19,6 +19,8 @@ enum { DRUA, DRANAP, DMGW, + DHNB, + DCN, }; #define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \ @@ -201,3 +203,5 @@ static inline bool hnb_gw_is_gtp_mapping_enabled(const struct hnb_gw *gw) { return gw->config.pfcp.remote_addr != NULL; } + +struct msgb *hnbgw_ranap_msg_alloc(const char *name); diff --git a/include/osmocom/hnbgw/hnbgw_cn.h b/include/osmocom/hnbgw/hnbgw_cn.h index b481a69..0df2716 100644 --- a/include/osmocom/hnbgw/hnbgw_cn.h +++ b/include/osmocom/hnbgw/hnbgw_cn.h @@ -3,3 +3,5 @@ #include int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip); + +const struct osmo_sccp_addr *hnbgw_cn_get_remote_addr(struct hnb_gw *gw, bool is_ps); diff --git a/include/osmocom/hnbgw/mgw_fsm.h b/include/osmocom/hnbgw/mgw_fsm.h index 8b14eaa..d4e989d 100644 --- a/include/osmocom/hnbgw/mgw_fsm.h +++ b/include/osmocom/hnbgw/mgw_fsm.h @@ -2,6 +2,6 @@ #include -int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message); -int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message); +int handle_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message); +int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message); int mgw_fsm_release(struct hnbgw_context_map *map); diff --git a/include/osmocom/hnbgw/ps_rab_ass_fsm.h b/include/osmocom/hnbgw/ps_rab_ass_fsm.h index 775d73a..0728b0d 100644 --- a/include/osmocom/hnbgw/ps_rab_ass_fsm.h +++ b/include/osmocom/hnbgw/ps_rab_ass_fsm.h @@ -9,6 +9,6 @@ enum ps_rab_ass_fsm_event { PS_RAB_ASS_EV_RAB_FAIL, }; -int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message); -int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message); +int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message); +int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message); void hnbgw_gtpmap_release(struct hnbgw_context_map *map); diff --git a/include/osmocom/hnbgw/tdefs.h b/include/osmocom/hnbgw/tdefs.h index 8ae1c97..6fee79b 100644 --- a/include/osmocom/hnbgw/tdefs.h +++ b/include/osmocom/hnbgw/tdefs.h @@ -4,4 +4,5 @@ extern struct osmo_tdef mgw_fsm_T_defs[]; extern struct osmo_tdef ps_T_defs[]; +extern struct osmo_tdef cmap_T_defs[]; extern struct osmo_tdef_group hnbgw_tdef_group[]; diff --git a/src/osmo-hnbgw/Makefile.am b/src/osmo-hnbgw/Makefile.am index a6cf5ea..d1179e4 100644 --- a/src/osmo-hnbgw/Makefile.am +++ b/src/osmo-hnbgw/Makefile.am @@ -37,6 +37,8 @@ osmo_hnbgw_SOURCES = \ hnbgw_ranap.c \ hnbgw_vty.c \ context_map.c \ + context_map_rua.c \ + context_map_sccp.c \ hnbgw_cn.c \ ranap_rab_ass.c \ mgw_fsm.c \ diff --git a/src/osmo-hnbgw/context_map.c b/src/osmo-hnbgw/context_map.c index 9816918..31f635d 100644 --- a/src/osmo-hnbgw/context_map.c +++ b/src/osmo-hnbgw/context_map.c @@ -34,13 +34,24 @@ #include const struct value_string hnbgw_context_map_state_names[] = { - {MAP_S_NULL , "not-initialized"}, - {MAP_S_ACTIVE , "active"}, - {MAP_S_RESERVED1, "inactive-reserved"}, - {MAP_S_RESERVED2, "inactive-discard"}, - {0, NULL} + { MAP_S_CONNECTING, "connecting" }, + { MAP_S_ACTIVE, "active" }, + { MAP_S_DISCONNECTING, "disconnecting" }, + {} }; +/* Combine the RUA and SCCP states, for VTY reporting only. */ +enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map) +{ + enum hnbgw_context_map_state rua = map_rua_get_state(map); + enum hnbgw_context_map_state sccp = map_sccp_get_state(map); + if (rua == MAP_S_ACTIVE && sccp == MAP_S_ACTIVE) + return MAP_S_ACTIVE; + if (rua == MAP_S_DISCONNECTING || sccp == MAP_S_DISCONNECTING) + return MAP_S_DISCONNECTING; + return MAP_S_CONNECTING; +} + /* is a given SCCP USER SAP Connection ID in use for a given CN link? */ static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id) { @@ -92,11 +103,15 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, uint32_t new_scu_conn_id; llist_for_each_entry(map, &hnb->map_list, hnb_list) { - if (map->state != MAP_S_ACTIVE) + if (map->cn_link != cn_if_new) continue; - if (map->cn_link != cn_if_new) { + + /* Matching on RUA context id -- only match for RUA context that has not been disconnected yet. If an + * inactive context map for a rua_ctx_id is still around, we may have two entries for the same + * rua_ctx_id around at the same time. That should only stay until its SCCP side is done releasing. */ + if (!map_rua_is_active(map)) continue; - } + if (map->rua_ctx_id == rua_ctx_id && map->is_ps == is_ps) { return map; @@ -113,7 +128,7 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, /* allocate a new map entry. */ map = talloc_zero(hnb, struct hnbgw_context_map); - map->state = MAP_S_NULL; + map->gw = hnb->gw; map->cn_link = cn_if_new; map->hnb_ctx = hnb; map->rua_ctx_id = rua_ctx_id; @@ -122,14 +137,43 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id, INIT_LLIST_HEAD(&map->ps_rab_ass); INIT_LLIST_HEAD(&map->ps_rabs); + map_rua_fsm_alloc(map); + map_sccp_fsm_alloc(map); + /* put it into both lists */ llist_add_tail(&map->hnb_list, &hnb->map_list); llist_add_tail(&map->cn_list, &cn_if_new->map_list); - map->state = MAP_S_ACTIVE; return map; } +int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg, + const char *file, int line) +{ + OSMO_ASSERT(map); + if (!map->rua_fi) { + LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive RUA events\n"); + return -EINVAL; + } + return _osmo_fsm_inst_dispatch(map->rua_fi, event, ranap_msg, file, line); +} + +int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg, + const char *file, int line) +{ + OSMO_ASSERT(map); + if (!map->sccp_fi) { + LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive SCCP events\n"); + return -EINVAL; + } + return _osmo_fsm_inst_dispatch(map->sccp_fi, event, ranap_msg, file, line); +} + +unsigned int msg_has_l2_data(const struct msgb *msg) +{ + return msg && msgb_l2(msg) ? msgb_l2len(msg) : 0; +} + /* Map from a CN + Connection ID to HNB + Context ID */ struct hnbgw_context_map * context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id) @@ -137,8 +181,12 @@ context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id) struct hnbgw_context_map *map; llist_for_each_entry(map, &cn->map_list, cn_list) { - if (map->state != MAP_S_ACTIVE) + /* Matching on SCCP conn id -- only match for SCCP conn that has not been disconnected yet. If an + * inactive context map for an scu_conn_id is still around, we may have two entries for the same + * scu_conn_id around at the same time. That should only stay until its RUA side is done releasing. */ + if (!map_sccp_is_active(map)) continue; + if (map->scu_conn_id == scu_conn_id) { return map; } @@ -152,23 +200,37 @@ context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id) void context_map_hnb_released(struct hnbgw_context_map *map) { - LOG_MAP(map, DMAIN, LOGL_INFO, "Deactivating\n"); + /* When a HNB disconnects from RUA, the hnb_context will be freed. This hnbgw_context_map was allocated as a + * child of the hnb_context and would also be deallocated along with the hnb_context. However, the SCCP side for + * this hnbgw_context_map may still be waiting for a graceful release (SCCP RLC). Move this hnbgw_context_map to + * the global hnb_gw talloc ctx, so it can stay around for graceful release / for SCCP timeout. + * + * We could also always allocate hnbgw_context_map under hnb_gw, but it is nice to see which hnb_context owns + * which hnbgw_context_map in a talloc report. + */ + talloc_steal(map->gw, map); - /* set the state to reserved. We still show up in the list and - * avoid re-allocation of the context-id until we are cleaned up - * by the context_map garbage collector timer */ + /* Tell RUA that the HNB is gone. SCCP release will follow via FSM events. */ + map_rua_dispatch(map, MAP_RUA_EV_HNB_LINK_LOST, NULL); +} - if (map->state != MAP_S_RESERVED2) - map->state = MAP_S_RESERVED1; - - /* Is SCCP still active and needs to be disconnected ungracefully? */ - if (map->scu_conn_active) { - osmo_sccp_tx_disconn(map->hnb_ctx->gw->sccp.cnlink->sccp_user, map->scu_conn_id, NULL, 0); - map->scu_conn_active = false; +void context_map_free(struct hnbgw_context_map *map) +{ + /* guard against FSM termination infinitely looping back here */ + if (map->deallocating) { + LOG_MAP(map, DMAIN, LOGL_DEBUG, "context_map_free(): already deallocating\n"); + return; } + map->deallocating = true; + + if (map->rua_fi) + osmo_fsm_inst_term(map->rua_fi, OSMO_FSM_TERM_REGULAR, NULL); + OSMO_ASSERT(map->rua_fi == NULL); + + if (map->sccp_fi) + osmo_fsm_inst_term(map->sccp_fi, OSMO_FSM_TERM_REGULAR, NULL); + OSMO_ASSERT(map->sccp_fi == NULL); - /* a possibly still existing MGW FSM must be terminated when the context - * map is deactivated. (this is a cornercase) */ if (map->mgw_fi) { mgw_fsm_release(map); OSMO_ASSERT(map->mgw_fi == NULL); @@ -177,46 +239,21 @@ void context_map_hnb_released(struct hnbgw_context_map *map) #if ENABLE_PFCP hnbgw_gtpmap_release(map); #endif + + if (map->cn_link) + llist_del(&map->cn_list); + if (map->hnb_ctx) + llist_del(&map->hnb_list); + + LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n"); + talloc_free(map); } -static struct osmo_timer_list context_map_tmr; - -static void context_map_tmr_cb(void *data) +void context_map_check_released(struct hnbgw_context_map *map) { - struct hnb_gw *gw = data; - struct hnbgw_cnlink *cn = gw->sccp.cnlink; - struct hnbgw_context_map *map, *next_map; - - DEBUGP(DMAIN, "Running context mapper garbage collection\n"); - llist_for_each_entry_safe(map, next_map, &cn->map_list, cn_list) { - switch (map->state) { - case MAP_S_RESERVED1: - /* first time we see this reserved - * entry: mark it for stage 2 */ - map->state = MAP_S_RESERVED2; - break; - case MAP_S_RESERVED2: - /* second time we see this reserved - * entry: remove it */ - LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n"); - map->state = MAP_S_NULL; - llist_del(&map->cn_list); - llist_del(&map->hnb_list); - talloc_free(map); - break; - default: - break; - } + if (map_rua_is_active(map) || map_sccp_is_active(map)) { + /* still active, do not release yet. */ + return; } - /* re-schedule this timer */ - osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0); -} - -int context_map_init(struct hnb_gw *gw) -{ - context_map_tmr.cb = context_map_tmr_cb; - context_map_tmr.data = gw; - osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0); - - return 0; + context_map_free(map); } diff --git a/src/osmo-hnbgw/context_map_rua.c b/src/osmo-hnbgw/context_map_rua.c new file mode 100644 index 0000000..25304d9 --- /dev/null +++ b/src/osmo-hnbgw/context_map_rua.c @@ -0,0 +1,361 @@ +/* RUA side FSM of hnbgw_context_map */ +/* (C) 2023 by sysmocom - s.m.f.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 + +enum map_rua_fsm_state { + MAP_RUA_ST_INIT, + MAP_RUA_ST_CONNECTED, + MAP_RUA_ST_DISCONNECTED, +}; + +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 }, +}; + +/* 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, \ + cmap_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-RUA-%u", hnb_context_name(map->hnb_ctx), 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: + 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: + 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_DISCONNECTED); + return 0; + + case MAP_RUA_ST_DISCONNECTED: + 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; +} + +/* Dispatch RANAP message to SCCP, if any. */ +static int handle_rx_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg) +{ + int rc; + if (!msg_has_l2_data(ranap_msg)) + return 0; + + /* 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. */ + if (!map->is_ps) { + ranap_message *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_set_destructor(message, destruct_ranap_cn_rx_co_ies); + + 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_rab_ass_resp(map, ranap_msg, message); + } + } +#if ENABLE_PFCP + } else if (hnb_gw_is_gtp_mapping_enabled(map->hnb_gw)) { + /* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */ + ranap_message *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_set_destructor(message, destruct_ranap_cn_rx_co_ies); + + 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 hnbgw_context_map *map = fi->priv; + struct msgb *ranap_msg = data; + + switch (event) { + + case MAP_RUA_EV_RX_CONNECT: + map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED); + /* The Connect will never be a RAB Assignment response, so no need for handle_rx_rua() (which decodes + * the RANAP message to detect a RAB Assignment response). Just forward to SCCP as is. */ + map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg); + return; + + case MAP_RUA_EV_RX_DISCONNECT: + case MAP_RUA_EV_CN_DISC: + case MAP_RUA_EV_HNB_LINK_LOST: + /* Unlikely that SCCP is active, but let the SCCP FSM decide about that. */ + handle_rx_rua(map, 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; + + 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, + }; + 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(map, 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(map, 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_DISCONNECTED); + 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_DISCONNECTED); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct hnbgw_context_map *map = fi->priv; + /* For sanity, always tell SCCP to disconnect, if it hasn't done so. Dispatching MAP_SCCP_EV_RAN_DISC may send + * SCCP into MAP_RUA_ST_DISCONNECTED, which calls context_map_check_released() and frees the hnbgw_context_map, + * so don't free it a second time. If SCCP stays active, calling context_map_check_released() has no effect. */ + if (map_sccp_is_active(map)) + map_sccp_dispatch(map, MAP_SCCP_EV_RAN_DISC, NULL); + else + context_map_check_released(map); +} + +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"); +} + +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; + context_map_check_released(map); +} + +#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) + , + .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) + , + .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, + }, +}; + +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); +} diff --git a/src/osmo-hnbgw/context_map_sccp.c b/src/osmo-hnbgw/context_map_sccp.c new file mode 100644 index 0000000..3042636 --- /dev/null +++ b/src/osmo-hnbgw/context_map_sccp.c @@ -0,0 +1,546 @@ +/* SCCP side FSM of hnbgw_context_map */ +/* (C) 2023 by sysmocom - s.m.f.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 + +enum map_sccp_fsm_state { + MAP_SCCP_ST_INIT, + MAP_SCCP_ST_WAIT_CC, + MAP_SCCP_ST_CONNECTED, + MAP_SCCP_ST_WAIT_RLSD, + MAP_SCCP_ST_DISCONNECTED, +}; + +static const struct value_string map_sccp_fsm_event_names[] = { + OSMO_VALUE_STRING(MAP_SCCP_EV_RX_CONNECTION_CONFIRM), + OSMO_VALUE_STRING(MAP_SCCP_EV_RX_DATA_INDICATION), + OSMO_VALUE_STRING(MAP_SCCP_EV_TX_DATA_REQUEST), + OSMO_VALUE_STRING(MAP_SCCP_EV_RAN_DISC), + OSMO_VALUE_STRING(MAP_SCCP_EV_RX_RELEASED), + {} +}; + +static struct osmo_fsm map_sccp_fsm; + +static const struct osmo_tdef_state_timeout map_sccp_fsm_timeouts[32] = { + [MAP_SCCP_ST_INIT] = { .T = -31 }, + [MAP_SCCP_ST_WAIT_CC] = { .T = -31 }, + [MAP_SCCP_ST_CONNECTED] = { .T = 0 }, + [MAP_SCCP_ST_WAIT_RLSD] = { .T = -31 }, + [MAP_SCCP_ST_DISCONNECTED] = { .T = -31 }, +}; + +/* Transition to a state, using the T timer defined in map_sccp_fsm_timeouts. + * Assumes local variable fi exists. */ +#define map_sccp_fsm_state_chg(state) \ + OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \ + map_sccp_fsm_timeouts, \ + cmap_T_defs, \ + 5) == 0) + +void map_sccp_fsm_alloc(struct hnbgw_context_map *map) +{ + struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_sccp_fsm, map, map, LOGL_DEBUG, NULL); + OSMO_ASSERT(fi); + osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-SCCP-%u", hnb_context_name(map->hnb_ctx), map->scu_conn_id); + + OSMO_ASSERT(map->sccp_fi == NULL); + map->sccp_fi = fi; + + /* trigger the timeout */ + map_sccp_fsm_state_chg(MAP_SCCP_ST_INIT); +} + +enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map) +{ + if (!map || !map->sccp_fi) + return MAP_S_DISCONNECTING; + switch (map->sccp_fi->state) { + case MAP_SCCP_ST_INIT: + case MAP_SCCP_ST_WAIT_CC: + return MAP_S_CONNECTING; + case MAP_SCCP_ST_CONNECTED: + return MAP_S_ACTIVE; + default: + case MAP_SCCP_ST_WAIT_RLSD: + case MAP_SCCP_ST_DISCONNECTED: + return MAP_S_DISCONNECTING; + } +} + +bool map_sccp_is_active(struct hnbgw_context_map *map) +{ + if (!map || !map->sccp_fi) + return false; + switch (map->sccp_fi->state) { + case MAP_SCCP_ST_DISCONNECTED: + return false; + default: + return true; + } +} + +static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) +{ + struct hnbgw_context_map *map = fi->priv; + struct osmo_scu_prim *prim; + int rc; + + if (!ranap_msg) { + /* prepare a msgb to send an empty N-Connect prim (but this should never happen in practice) */ + ranap_msg = hnbgw_ranap_msg_alloc("SCCP-CR-empty"); + } + + prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, ranap_msg); + prim->u.connect.called_addr = *hnbgw_cn_get_remote_addr(map->gw, map->is_ps); + prim->u.connect.calling_addr = map->gw->sccp.local_addr; + prim->u.connect.sccp_class = 2; + prim->u.connect.conn_id = map->scu_conn_id; + + rc = osmo_sccp_user_sap_down_nofree(map->cn_link->sccp_user, &prim->oph); + if (rc) + LOGPFSML(fi, LOGL_ERROR, "Failed to forward SCCP Connectoin Request to CN\n"); + return rc; +} + +static int tx_sccp_df1(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) +{ + struct hnbgw_context_map *map = fi->priv; + struct osmo_scu_prim *prim; + int rc; + + if (!msg_has_l2_data(ranap_msg)) + return 0; + + prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim)); + osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, ranap_msg); + prim->u.data.conn_id = map->scu_conn_id; + + rc = osmo_sccp_user_sap_down_nofree(map->cn_link->sccp_user, &prim->oph); + if (rc) + LOGPFSML(fi, LOGL_ERROR, "Failed to forward SCCP Data Form 1 to CN\n"); + return rc; +} + +static int tx_sccp_rlsd(struct osmo_fsm_inst *fi) +{ + struct hnbgw_context_map *map = fi->priv; + return osmo_sccp_tx_disconn(map->cn_link->sccp_user, map->scu_conn_id, NULL, 0); +} + +static int destruct_ranap_ran_rx_co_ies(ranap_message *ranap_message_p) +{ + ranap_ran_rx_co_free(ranap_message_p); + return 0; +} + +static int handle_rx_sccp(struct osmo_fsm_inst *fi, struct msgb *ranap_msg) +{ + struct hnbgw_context_map *map = fi->priv; + int rc; + + /* When there was no message received along with the received event, then there is nothing to forward to RUA. */ + if (!msg_has_l2_data(ranap_msg)) + return 0; + + /* See if it is a RAB Assignment Request message from SCCP to RUA, where we need to change the user plane + * information, for RTP mapping via MGW, or GTP mapping via UPF. */ + if (!map->is_ps) { + ranap_message *message; + /* Circuit-Switched. Set up mapping of RTP ports via MGW */ + message = talloc_zero(OTC_SELECT, ranap_message); + rc = ranap_ran_rx_co_decode(message, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg)); + + if (rc == 0) { + talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies); + + switch (message->procedureCode) { + case RANAP_ProcedureCode_id_RAB_Assignment: + /* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */ + return handle_rab_ass_req(map, ranap_msg, message); + case RANAP_ProcedureCode_id_Iu_Release: + /* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the + * FSM code. It is just forwarded normally by map_rua_tx_dt() below. */ + mgw_fsm_release(map); + break; + } + } +#if ENABLE_PFCP + } else { + ranap_message *message; + struct hnb_gw *hnb_gw = cnlink->gw; + /* Packet-Switched. Set up mapping of GTP ports via UPF */ + message = talloc_zero(OTC_SELECT, ranap_message); + rc = ranap_ran_rx_co_decode(message, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg)); + + if (rc == 0) { + talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies); + + switch (message->procedureCode) { + + case RANAP_ProcedureCode_id_RAB_Assignment: + /* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the + * GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */ + if (hnb_gw_is_gtp_mapping_enabled(hnb_gw)) { + LOGP(DMAIN, LOGL_DEBUG, + "RAB Assignment: setting up GTP tunnel mapping via UPF %s\n", + osmo_sockaddr_to_str_c(OTC_SELECT, &hnb_gw->pfcp.cp_peer->remote_addr)); + return hnbgw_gtpmap_rx_rab_ass_req(map, ranap_msg, message); + } + /* If no UPF is configured, directly forward the message as-is (no GTP mapping). */ + LOGP(DMAIN, LOGL_DEBUG, "RAB Assignment: no UPF configured, forwarding as-is\n"); + break; + + case RANAP_ProcedureCode_id_Iu_Release: + /* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the + * FSM code. It is just forwarded normally by map_rua_tx_dt() below. */ + hnbgw_gtpmap_release(map); + break; + } + } +#endif + } + + /* It was not a RAB Assignment Request that needed to be intercepted. Forward as-is to RUA. */ + return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg); +} + +static void map_sccp_init_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_SCCP_EV_TX_DATA_REQUEST: + /* In the INIT state, the first MAP_SCCP_EV_TX_DATA_REQUEST will be the RANAP message received from the + * RUA Connect message. Send the SCCP CR and transition to WAIT_CC. */ + if (tx_sccp_cr(fi, ranap_msg) == 0) + map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_CC); + return; + + case MAP_SCCP_EV_RAN_DISC: + /* No CR has been sent yet, just go to disconnected state. */ + if (msg_has_l2_data(ranap_msg)) + LOG_MAP(map, DLSCCP, LOGL_ERROR, "SCCP not connected, cannot dispatch RANAP message\n"); + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return; + + case MAP_SCCP_EV_RX_RELEASED: + /* SCCP RLSD received from CN. This will never happen since we haven't even asked for a connection, but + * for completeness: */ + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void map_sccp_wait_cc_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_SCCP_EV_RX_CONNECTION_CONFIRM: + map_sccp_fsm_state_chg(MAP_SCCP_ST_CONNECTED); + /* Usually doesn't but if the SCCP CC contained data, forward it to RUA */ + handle_rx_sccp(fi, ranap_msg); + return; + + case MAP_SCCP_EV_TX_DATA_REQUEST: + LOGPFSML(fi, LOGL_ERROR, "Connection not yet confirmed, cannot forward RANAP to CN\n"); + return; + + case MAP_SCCP_EV_RAN_DISC: + /* RUA connection was terminated. First wait for the CC before releasing the SCCP conn. */ + if (msg_has_l2_data(ranap_msg)) + LOGPFSML(fi, LOGL_ERROR, "Connection not yet confirmed, cannot forward RANAP to CN\n"); + map->please_disconnect = true; + return; + + case MAP_SCCP_EV_RX_RELEASED: + /* SCCP RLSD received from CN. This will never happen since we haven't even received a Connection + * Confirmed, but for completeness: */ + handle_rx_sccp(fi, ranap_msg); + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct hnbgw_context_map *map = fi->priv; + if (map->please_disconnect) { + /* SCCP has already been asked to disconnect, so disconnect now that the CC has been received. Send RLSD + * to SCCP (without RANAP data) */ + tx_sccp_rlsd(fi); + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + } +} + +static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct msgb *ranap_msg = data; + + switch (event) { + + case MAP_SCCP_EV_RX_DATA_INDICATION: + /* forward RANAP from SCCP to RUA */ + handle_rx_sccp(fi, ranap_msg); + return; + + case MAP_SCCP_EV_TX_DATA_REQUEST: + /* Someone (usually the RUA side) wants us to send a RANAP payload to CN via SCCP */ + tx_sccp_df1(fi, ranap_msg); + return; + + case MAP_SCCP_EV_RAN_DISC: + /* RUA has disconnected, and usually has sent an Iu-ReleaseComplete along with its RUA Disconnect. On + * SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data (SCCP Data Form 1), and we will + * expect the CN to send an SCCP RLSD soon. */ + map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_RLSD); + tx_sccp_df1(fi, ranap_msg); + return; + + case MAP_SCCP_EV_RX_RELEASED: + /* The CN sends an N-Disconnect (SCCP Released) out of the usual sequence. Not what we expected, but + * handle it. */ + LOGPFSML(fi, LOGL_ERROR, "CN sends SCCP Released sooner than expected\n"); + handle_rx_sccp(fi, ranap_msg); + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return; + + case MAP_SCCP_EV_RX_CONNECTION_CONFIRM: + /* Already connected. Unusual, but if there is data just forward it. */ + LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC again\n"); + handle_rx_sccp(fi, ranap_msg); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void map_sccp_wait_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct hnbgw_context_map *map = fi->priv; + /* For sanity, always tell RUA to disconnect, if it hasn't done so. */ + if (map_rua_is_active(map)) + map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL); +} + +static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct msgb *ranap_msg = data; + + switch (event) { + + case MAP_SCCP_EV_RX_RELEASED: + /* The CN sends the expected SCCP RLSD. + * Usually there is no data, but if there is just forward it. + * Usually RUA is already disconnected, but let the RUA FSM decide about that. */ + handle_rx_sccp(fi, ranap_msg); + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return; + + case MAP_SCCP_EV_RX_DATA_INDICATION: + /* RUA is probably already disconnected, but let the RUA FSM decide about that. */ + handle_rx_sccp(fi, ranap_msg); + return; + + case MAP_SCCP_EV_TX_DATA_REQUEST: + case MAP_SCCP_EV_RAN_DISC: + /* Normally, RUA would already disconnected, but since SCCP is officially still connected, we can still + * forward messages there. Already waiting for CN to send the SCCP RLSD. If there is a message, forward + * it, and just continue to time out on the SCCP RLSD. */ + tx_sccp_df1(fi, ranap_msg); + return; + + case MAP_SCCP_EV_RX_CONNECTION_CONFIRM: + /* Already connected. Unusual, but if there is data just forward it. */ + LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC\n"); + handle_rx_sccp(fi, ranap_msg); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void map_sccp_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct hnbgw_context_map *map = fi->priv; + /* For sanity, always tell RUA to disconnect, if it hasn't done so. Dispatching MAP_RUA_EV_CN_DISC may send + * RUA into MAP_RUA_ST_DISCONNECTED, which calls context_map_check_released() and frees the hnbgw_context_map, + * so don't free it a second time. If RUA stays active, calling context_map_check_released() has no effect. */ + if (map_rua_is_active(map)) + map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL); + else + context_map_check_released(map); +} + +static void map_sccp_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, "SCCP not connected, cannot dispatch RANAP message\n"); +} + +static int map_sccp_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct hnbgw_context_map *map = fi->priv; + + /* Return 1 to terminate FSM instance, 0 to keep running */ + switch (fi->state) { + case MAP_SCCP_ST_INIT: + case MAP_SCCP_ST_WAIT_CC: + /* cannot sent SCCP RLSD, because the other side hasn't responded with the remote reference. */ + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return 0; + + case MAP_SCCP_ST_CONNECTED: + case MAP_SCCP_ST_WAIT_RLSD: + /* send SCCP RLSD. libosmo-sigtran/sccp_scoc.c will do the SCCP connection cleanup. + * (It will repeatedly send SCCP RLSD until the peer responded with SCCP RLC, or until the + * sccp_connection->t_int timer expires, and the sccp_connection is freed.) */ + if (map->cn_link && map->cn_link->sccp_user) + tx_sccp_rlsd(fi); + map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED); + return 0; + + default: + case MAP_SCCP_ST_DISCONNECTED: + return 1; + } +} + +void map_sccp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct hnbgw_context_map *map = fi->priv; + map->sccp_fi = NULL; +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state map_sccp_fsm_states[] = { + [MAP_SCCP_ST_INIT] = { + .name = "init", + .in_event_mask = 0 + | S(MAP_SCCP_EV_TX_DATA_REQUEST) + | S(MAP_SCCP_EV_RAN_DISC) + | S(MAP_SCCP_EV_RX_RELEASED) + , + .out_state_mask = 0 + | S(MAP_SCCP_ST_INIT) + | S(MAP_SCCP_ST_WAIT_CC) + | S(MAP_SCCP_ST_DISCONNECTED) + , + .action = map_sccp_init_action, + }, + [MAP_SCCP_ST_WAIT_CC] = { + .name = "wait_cc", + .in_event_mask = 0 + | S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM) + | S(MAP_SCCP_EV_TX_DATA_REQUEST) + | S(MAP_SCCP_EV_RAN_DISC) + | S(MAP_SCCP_EV_RX_RELEASED) + , + .out_state_mask = 0 + | S(MAP_SCCP_ST_CONNECTED) + | S(MAP_SCCP_ST_DISCONNECTED) + , + .action = map_sccp_wait_cc_action, + }, + [MAP_SCCP_ST_CONNECTED] = { + .name = "connected", + .in_event_mask = 0 + | S(MAP_SCCP_EV_RX_DATA_INDICATION) + | S(MAP_SCCP_EV_TX_DATA_REQUEST) + | S(MAP_SCCP_EV_RAN_DISC) + | S(MAP_SCCP_EV_RX_RELEASED) + | S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM) + , + .out_state_mask = 0 + | S(MAP_SCCP_ST_WAIT_RLSD) + | S(MAP_SCCP_ST_DISCONNECTED) + , + .onenter = map_sccp_connected_onenter, + .action = map_sccp_connected_action, + }, + [MAP_SCCP_ST_WAIT_RLSD] = { + .name = "wait_rlsd", + .in_event_mask = 0 + | S(MAP_SCCP_EV_RX_RELEASED) + | S(MAP_SCCP_EV_RX_DATA_INDICATION) + | S(MAP_SCCP_EV_TX_DATA_REQUEST) + | S(MAP_SCCP_EV_RAN_DISC) + | S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM) + , + .out_state_mask = 0 + | S(MAP_SCCP_ST_DISCONNECTED) + , + .onenter = map_sccp_wait_rlsd_onenter, + .action = map_sccp_wait_rlsd_action, + }, + [MAP_SCCP_ST_DISCONNECTED] = { + .name = "disconnected", + .in_event_mask = 0 + | S(MAP_SCCP_EV_TX_DATA_REQUEST) + | S(MAP_SCCP_EV_RAN_DISC) + , + .onenter = map_sccp_disconnected_onenter, + .action = map_sccp_disconnected_action, + }, +}; + +static struct osmo_fsm map_sccp_fsm = { + .name = "map_sccp", + .states = map_sccp_fsm_states, + .num_states = ARRAY_SIZE(map_sccp_fsm_states), + .log_subsys = DCN, + .event_names = map_sccp_fsm_event_names, + .timer_cb = map_sccp_fsm_timer_cb, + .cleanup = map_sccp_fsm_cleanup, +}; + +static __attribute__((constructor)) void map_sccp_fsm_register(void) +{ + OSMO_ASSERT(osmo_fsm_register(&map_sccp_fsm) == 0); +} diff --git a/src/osmo-hnbgw/hnbgw.c b/src/osmo-hnbgw/hnbgw.c index 322dabe..ce82282 100644 --- a/src/osmo-hnbgw/hnbgw.c +++ b/src/osmo-hnbgw/hnbgw.c @@ -98,8 +98,6 @@ static struct hnb_gw *hnb_gw_create(void *ctx) INIT_LLIST_HEAD(&gw->hnb_list); INIT_LLIST_HEAD(&gw->ue_list); - context_map_init(gw); - gw->mgw_pool = mgcp_client_pool_alloc(gw); gw->config.mgcp_client = talloc_zero(tall_hnb_ctx, struct mgcp_client_conf); mgcp_client_conf_init(gw->config.mgcp_client); @@ -397,18 +395,16 @@ void hnb_context_release_ue_state(struct hnb_context *ctx) /* deactivate all context maps */ llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) { - /* remove it from list, as HNB context will soon be - * gone. Let's hope the second osmo_llist_del in the - * map garbage collector works fine? */ - llist_del(&map->hnb_list); - llist_del(&map->cn_list); context_map_hnb_released(map); + /* hnbgw_context_map will remove itself from lists when it is ready. */ } ue_context_free_by_hnb(ctx->gw, ctx); } void hnb_context_release(struct hnb_context *ctx) { + struct hnbgw_context_map *map; + LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n"); /* remove from the list of HNB contexts */ @@ -424,6 +420,14 @@ void hnb_context_release(struct hnb_context *ctx) osmo_stream_srv_destroy(ctx->conn); } /* else: we are called from closed_cb, so conn is being freed separately */ + /* hnbgw_context_map are still listed in ctx->map_list, but we are freeing ctx. Remove all entries from the + * list, but keep the hnbgw_context_map around for graceful release. They are also listed under + * hnbgw_cnlink->map_list, and will remove themselves when ready. */ + while ((map = llist_first_entry_or_null(&ctx->map_list, struct hnbgw_context_map, hnb_list))) { + llist_del(&map->hnb_list); + map->hnb_ctx = NULL; + } + talloc_free(ctx); } @@ -469,6 +473,16 @@ static const struct log_info_cat log_cat[] = { .color = "\033[1;33m", .description = "Media Gateway", }, + [DHNB] = { + .name = "DHNB", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = OSMO_LOGCOLOR_CYAN, + .description = "HNB side (via RUA)", + }, + [DCN] = { + .name = "DCN", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = OSMO_LOGCOLOR_DARKYELLOW, + .description = "Core Network side (via SCCP)", + }, }; static const struct log_info hnbgw_log_info = { @@ -841,3 +855,12 @@ int main(int argc, char **argv) /* not reached */ exit(0); } + +struct msgb *hnbgw_ranap_msg_alloc(const char *name) +{ + struct msgb *ranap_msg; + ranap_msg = msgb_alloc_c(OTC_SELECT, sizeof(struct osmo_scu_prim) + 1500, name); + msgb_reserve(ranap_msg, sizeof(struct osmo_scu_prim)); + ranap_msg->l2h = ranap_msg->data; + return ranap_msg; +} diff --git a/src/osmo-hnbgw/hnbgw_cn.c b/src/osmo-hnbgw/hnbgw_cn.c index 4c0e685..731c758 100644 --- a/src/osmo-hnbgw/hnbgw_cn.c +++ b/src/osmo-hnbgw/hnbgw_cn.c @@ -345,16 +345,13 @@ static int handle_cn_conn_conf(struct hnbgw_cnlink *cnlink, osmo_sccp_addr_to_str_c(OTC_SELECT, ss7, ¶m->calling_addr), osmo_sccp_addr_to_str_c(OTC_SELECT, ss7, ¶m->responding_addr)); - /* Nothing needs to happen for RUA, RUA towards the HNB doesn't seem to know any confirmations to its CONNECT - * operation. */ - map = context_map_by_cn(cnlink, param->conn_id); - if (!map) + if (!map) { + /* We have no such SCCP connection. Ignore. */ return 0; + } - /* SCCP connection is confirmed. Mark conn as active, i.e. requires a DISCONNECT to clean up the SCCP - * connection. */ - map->scu_conn_active = true; + map_sccp_dispatch(map, MAP_SCCP_EV_RX_CONNECTION_CONFIRM, oph->msg); return 0; } @@ -363,79 +360,14 @@ static int handle_cn_data_ind(struct hnbgw_cnlink *cnlink, struct osmo_prim_hdr *oph) { struct hnbgw_context_map *map; - ranap_message *message; - int rc; - - /* Usually connection-oriented data is always passed transparently towards the specific HNB, via a RUA - * connection identified by conn_id. An exception is made for RANAP RAB AssignmentRequest and - * RANAP RAB AssignmentResponse, since those messages contain transport layer information (RTP stream IP/Port), - * which is rewritten by the FSM that controls the co-located media gateway. */ map = context_map_by_cn(cnlink, param->conn_id); if (!map) { - /* FIXME: Return an error / released primitive */ + /* We have no such SCCP connection. Ignore. */ return 0; } - /* Intercept RAB Assignment Request, to map RTP and GTP between access and core */ - if (!map->is_ps) { - /* Circuit-Switched. Set up mapping of RTP ports via MGW */ - message = talloc_zero(map, ranap_message); - rc = ranap_ran_rx_co_decode(map, message, msgb_l2(oph->msg), msgb_l2len(oph->msg)); - - if (rc == 0) { - switch (message->procedureCode) { - case RANAP_ProcedureCode_id_RAB_Assignment: - /* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */ - return handle_rab_ass_req(map, oph, message); - case RANAP_ProcedureCode_id_Iu_Release: - /* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the - * FSM code. It is just forwarded normally by the rua_tx_dt() call below. */ - mgw_fsm_release(map); - break; - } - ranap_ran_rx_co_free(message); - } - - talloc_free(message); -#if ENABLE_PFCP - } else { - struct hnb_gw *hnb_gw = cnlink->gw; - /* Packet-Switched. Set up mapping of GTP ports via UPF */ - message = talloc_zero(map, ranap_message); - rc = ranap_ran_rx_co_decode(map, message, msgb_l2(oph->msg), msgb_l2len(oph->msg)); - - if (rc == 0) { - switch (message->procedureCode) { - - case RANAP_ProcedureCode_id_RAB_Assignment: - /* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the - * GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */ - if (hnb_gw_is_gtp_mapping_enabled(hnb_gw)) { - LOGP(DMAIN, LOGL_DEBUG, - "RAB Assignment: setting up GTP tunnel mapping via UPF %s\n", - osmo_sockaddr_to_str_c(OTC_SELECT, &hnb_gw->pfcp.cp_peer->remote_addr)); - return hnbgw_gtpmap_rx_rab_ass_req(map, oph, message); - } - /* If no UPF is configured, directly forward the message as-is (no GTP mapping). */ - LOGP(DMAIN, LOGL_DEBUG, "RAB Assignment: no UPF configured, forwarding as-is\n"); - break; - - case RANAP_ProcedureCode_id_Iu_Release: - /* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the - * FSM code. It is just forwarded normally by the rua_tx_dt() call below. */ - hnbgw_gtpmap_release(map); - break; - } - ranap_ran_rx_co_free(message); - } - - talloc_free(message); -#endif - } - - return rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, - msgb_l2(oph->msg), msgb_l2len(oph->msg)); + return map_sccp_dispatch(map, MAP_SCCP_EV_RX_DATA_INDICATION, oph->msg); } static int handle_cn_disc_ind(struct hnbgw_cnlink *cnlink, @@ -449,22 +381,13 @@ static int handle_cn_disc_ind(struct hnbgw_cnlink *cnlink, LOGP(DMAIN, LOGL_DEBUG, "handle_cn_disc_ind() responding_addr=%s\n", inet_ntoa(param->responding_addr.ip.v4)); - RUA_Cause_t rua_cause = { - .present = RUA_Cause_PR_NOTHING, - /* FIXME: Convert incoming SCCP cause to RUA cause */ - }; - - /* we need to notify the HNB associated with this connection via - * a RUA DISCONNECT */ - map = context_map_by_cn(cnlink, param->conn_id); if (!map) { - /* FIXME: Return an error / released primitive */ + /* We have no connection. Ignore. */ return 0; } - return rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id, - &rua_cause, msgb_l2(oph->msg), msgb_l2len(oph->msg)); + return map_sccp_dispatch(map, MAP_SCCP_EV_RX_RELEASED, oph->msg); } /* Entry point for primitives coming up from SCCP User SAP */ @@ -492,6 +415,8 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx) return -1; } + talloc_steal(OTC_SELECT, oph->msg); + switch (OSMO_PRIM_HDR(oph)) { case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION): rc = handle_cn_unitdata(cnlink, &prim->u.unitdata, oph); @@ -512,8 +437,6 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx) break; } - msgb_free(oph->msg); - return rc; } @@ -637,3 +560,8 @@ int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port return 0; } + +const struct osmo_sccp_addr *hnbgw_cn_get_remote_addr(struct hnb_gw *gw, bool is_ps) +{ + return is_ps ? &gw->sccp.iups_remote_addr : &gw->sccp.iucs_remote_addr; +} diff --git a/src/osmo-hnbgw/hnbgw_rua.c b/src/osmo-hnbgw/hnbgw_rua.c index 97ced4a..db7fdbb 100644 --- a/src/osmo-hnbgw/hnbgw_rua.c +++ b/src/osmo-hnbgw/hnbgw_rua.c @@ -62,6 +62,11 @@ static int hnbgw_rua_tx(struct hnb_context *ctx, struct msgb *msg) if (!msg) return -EINVAL; + if (!ctx || !ctx->conn) { + LOGHNB(ctx, DRUA, LOGL_ERROR, "RUA context to this HNB is not connected, cannot transmit message\n"); + return -ENOTCONN; + } + msgb_sctp_ppid(msg) = IUH_PPI_RUA; osmo_stream_srv_send(ctx->conn, msg); @@ -198,23 +203,16 @@ static int rua_to_scu(struct hnb_context *hnb, uint32_t context_id, uint32_t cause, const uint8_t *data, unsigned int len) { - struct msgb *msg; - struct osmo_scu_prim *prim; + struct msgb *ranap_msg = NULL; struct hnbgw_context_map *map = NULL; struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink; - struct osmo_sccp_addr *remote_addr; bool is_ps; - bool release_context_map = false; - ranap_message *message; - int rc; switch (cN_DomainIndicator) { case RUA_CN_DomainIndicator_cs_domain: - remote_addr = &hnb->gw->sccp.iucs_remote_addr; is_ps = false; break; case RUA_CN_DomainIndicator_ps_domain: - remote_addr = &hnb->gw->sccp.iups_remote_addr; is_ps = true; break; default: @@ -227,9 +225,15 @@ static int rua_to_scu(struct hnb_context *hnb, return 0; } - msg = msgb_alloc(1500, "rua_to_sccp"); - - prim = (struct osmo_scu_prim *) msgb_put(msg, sizeof(*prim)); + /* If there is RANAP data, include it in the msgb. In RUA there is always data in practice, but theoretically it + * could be an empty Connect or Disconnect. */ + if (data && len) { + /* According to API doc of map_rua_fsm_event: allocate msgb for RANAP data from OTC_SELECT, reserve + * headroom for an osmo_scu_prim. Point l2h at the RANAP data. */ + ranap_msg = hnbgw_ranap_msg_alloc("RANAP_from_RUA"); + ranap_msg->l2h = msgb_put(ranap_msg, len); + memcpy(ranap_msg->l2h, data, len); + } map = context_map_alloc_by_hnb(hnb, context_id, is_ps, cn); OSMO_ASSERT(map); @@ -237,91 +241,21 @@ static int rua_to_scu(struct hnb_context *hnb, LOG_MAP(map, DRUA, LOGL_DEBUG, "rx RUA %s with %u bytes RANAP data\n", rua_procedure_code_name(rua_procedure), data ? len : 0); - /* add primitive header */ switch (rua_procedure) { case RUA_ProcedureCode_id_Connect: - osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, msg); - prim->u.connect.called_addr = *remote_addr; - prim->u.connect.calling_addr = cn->gw->sccp.local_addr; - prim->u.connect.sccp_class = 2; - prim->u.connect.conn_id = map->scu_conn_id; - /* Two separate logs because of osmo_sccp_addr_dump(). */ - LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: called_addr:%s\n", - osmo_sccp_addr_dump(&prim->u.connect.called_addr)); - LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA to SCCP N_CONNECT: calling_addr:%s\n", - osmo_sccp_addr_dump(&prim->u.connect.calling_addr)); - break; + return map_rua_dispatch(map, MAP_RUA_EV_RX_CONNECT, ranap_msg); case RUA_ProcedureCode_id_DirectTransfer: - osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, msg); - prim->u.data.conn_id = map->scu_conn_id; - break; + return map_rua_dispatch(map, MAP_RUA_EV_RX_DIRECT_TRANSFER, ranap_msg); case RUA_ProcedureCode_id_Disconnect: - osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_REQUEST, msg); - prim->u.disconnect.conn_id = map->scu_conn_id; - prim->u.disconnect.cause = cause; - release_context_map = true; - /* Mark SCCP conn as gracefully disconnected */ - map->scu_conn_active = false; - break; + return map_rua_dispatch(map, MAP_RUA_EV_RX_DISCONNECT, ranap_msg); default: /* No caller may ever pass a different RUA procedure code */ OSMO_ASSERT(false); } - - /* If there is RANAP data, include it in the msgb. Usually there is data, but this could also be an SCCP CR - * a.k.a. OSMO_SCU_PRIM_N_CONNECT without RANAP payload. */ - if (data && len) { - msg->l2h = msgb_put(msg, len); - memcpy(msg->l2h, data, len); - } - - /* If there is data, see if it is a RAB Assignment message where we need to change the user plane information, - * for RTP mapping via MGW (soon also GTP mapping via UPF). */ - if (data && len && map && !release_context_map) { - if (!map->is_ps) { - message = talloc_zero(map, ranap_message); - rc = ranap_cn_rx_co_decode2(message, msgb_l2(prim->oph.msg), msgb_l2len(prim->oph.msg)); - - if (rc == 0) { - 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_rab_ass_resp(map, &prim->oph, message); - } - ranap_cn_rx_co_free(message); - } - - talloc_free(message); -#if ENABLE_PFCP - } else if (hnb_gw_is_gtp_mapping_enabled(hnb->gw)) { - /* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */ - message = talloc_zero(map, ranap_message); - rc = ranap_cn_rx_co_decode2(message, msgb_l2(prim->oph.msg), msgb_l2len(prim->oph.msg)); - - if (rc == 0) { - 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, &prim->oph, message); - } - ranap_cn_rx_co_free(message); - } - - talloc_free(message); -#endif - } - } - - rc = osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph); - - if (map && release_context_map) - context_map_hnb_released(map); - - return rc; } static uint32_t rua_to_scu_cause(RUA_Cause_t *in) diff --git a/src/osmo-hnbgw/hnbgw_vty.c b/src/osmo-hnbgw/hnbgw_vty.c index abddc79..7f79b82 100644 --- a/src/osmo-hnbgw/hnbgw_vty.c +++ b/src/osmo-hnbgw/hnbgw_vty.c @@ -208,10 +208,8 @@ static void vty_dump_hnb_info(struct vty *vty, struct hnb_context *hnb) hnb->hnbap_stream, hnb->rua_stream, VTY_NEWLINE); llist_for_each_entry(map, &hnb->map_list, hnb_list) { - map_count[map->is_ps? 1 : 0]++; - state_count[map->is_ps? 1 : 0] - [(map->state >= 0 && map->state < MAP_S_NUM_STATES)? - map->state : MAP_S_NUM_STATES]++; + map_count[map->is_ps ? 1 : 0]++; + state_count[map->is_ps ? 1 : 0][context_map_get_state(map)]++; } vty_dump_hnb_info__map_states(vty, "IuCS", map_count[0], state_count[0]); vty_dump_hnb_info__map_states(vty, "IuPS", map_count[1], state_count[1]); diff --git a/src/osmo-hnbgw/mgw_fsm.c b/src/osmo-hnbgw/mgw_fsm.c index fe1e227..cddc64e 100644 --- a/src/osmo-hnbgw/mgw_fsm.c +++ b/src/osmo-hnbgw/mgw_fsm.c @@ -52,10 +52,7 @@ /* Send Iu Release Request, this is done in erroneous cases from which we cannot recover */ static void tx_release_req(struct hnbgw_context_map *map) { - struct hnb_context *hnb = map->hnb_ctx; - struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink; struct msgb *msg; - struct osmo_scu_prim *prim; static const struct RANAP_Cause cause = { .present = RANAP_Cause_PR_transmissionNetwork, .choice.transmissionNetwork = @@ -64,11 +61,8 @@ static void tx_release_req(struct hnbgw_context_map *map) msg = ranap_new_msg_iu_rel_req(&cause); msg->l2h = msg->data; - - prim = (struct osmo_scu_prim *)msgb_push(msg, sizeof(*prim)); - prim->u.data.conn_id = map->scu_conn_id; - osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, msg); - osmo_sccp_user_sap_down(cn->sccp_user, &prim->oph); + talloc_steal(OTC_SELECT, msg); + map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, msg); } #define S(x) (1 << (x)) @@ -112,7 +106,7 @@ struct mgw_fsm_priv { /* Pointers to messages and prim header we take ownership of */ ranap_message *ranap_rab_ass_req_message; ranap_message *ranap_rab_ass_resp_message; - struct osmo_prim_hdr *ranap_rab_ass_resp_oph; + struct msgb *ranap_rab_ass_resp_msgb; /* MGW context */ struct mgcp_client *mgcpc; @@ -173,7 +167,7 @@ static void mgw_fsm_crcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta mgw_info.codecs[0] = CODEC_IUFP; mgw_info.codecs_len = 1; - mgw_fsm_priv->mgcpc = mgcp_client_pool_get(map->hnb_ctx->gw->mgw_pool); + mgw_fsm_priv->mgcpc = mgcp_client_pool_get(map->gw->mgw_pool); if (!mgw_fsm_priv->mgcpc) { LOGPFSML(fi, LOGL_ERROR, "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n"); @@ -255,8 +249,9 @@ static void mgw_fsm_assign_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state } LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentRequest to HNB\n"); - rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msg->data, msg->len); - msgb_free(msg); + msg->l2h = msg->data; + talloc_steal(OTC_SELECT, msg); + map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg); } static void mgw_fsm_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data) @@ -274,8 +269,6 @@ static void mgw_fsm_mdcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta { struct mgw_fsm_priv *mgw_fsm_priv = fi->priv; struct hnbgw_context_map *map = mgw_fsm_priv->map; - struct hnb_context *hnb = map->hnb_ctx; - struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink; struct mgcp_conn_peer mgw_info; struct osmo_sockaddr addr; struct osmo_sockaddr_str addr_str; @@ -298,6 +291,8 @@ static void mgw_fsm_mdcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta if (rc < 0) { rab_failed_at_hnb = ranap_rab_ass_resp_ies_check_failure(ies, mgw_fsm_priv->rab_id); if (rab_failed_at_hnb) { + struct msgb *msg; + LOGPFSML(fi, LOGL_ERROR, "The RAB-AssignmentResponse contains a RAB-FailedList, RAB-Assignment (%u) failed.\n", mgw_fsm_priv->rab_id); @@ -305,8 +300,12 @@ static void mgw_fsm_mdcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta /* Forward the RAB-AssignmentResponse transparently. This will ensure that the MSC is informed * about the problem. */ LOGPFSML(fi, LOGL_DEBUG, "forwarding unmodified RAB-AssignmentResponse to MSC\n"); - rc = osmo_sccp_user_sap_down(cn->sccp_user, mgw_fsm_priv->ranap_rab_ass_resp_oph); - mgw_fsm_priv->ranap_rab_ass_resp_oph = NULL; + + msg = mgw_fsm_priv->ranap_rab_ass_resp_msgb; + mgw_fsm_priv->ranap_rab_ass_resp_msgb = NULL; + talloc_steal(OTC_SELECT, msg); + + rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, msg); if (rc < 0) { LOGPFSML(fi, LOGL_DEBUG, "failed to forward RAB-AssignmentResponse message\n"); osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0); @@ -431,15 +430,15 @@ static void mgw_fsm_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *dat * the original message. Ensure that there is enough room in l2h to grow. (The current implementation * should yield a message with the same size, but there is no guarantee for that) */ msg_max_len = - msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg) + - msgb_tailroom(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg); - rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg, - mgw_fsm_priv->ranap_rab_ass_resp_oph->msg->l2h, - msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), msg_max_len); + msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb) + + msgb_tailroom(mgw_fsm_priv->ranap_rab_ass_resp_msgb); + rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_msgb, + mgw_fsm_priv->ranap_rab_ass_resp_msgb->l2h, + msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), msg_max_len); OSMO_ASSERT(rc == 0); - rc = ranap_rab_ass_resp_encode(msgb_l2(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), - msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), ies); + rc = ranap_rab_ass_resp_encode(msgb_l2(mgw_fsm_priv->ranap_rab_ass_resp_msgb), + msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), ies); if (rc < 0) { LOGPFSML(fi, LOGL_ERROR, "failed to re-encode RAB-AssignmentResponse message\n"); osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0); @@ -447,9 +446,9 @@ static void mgw_fsm_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *dat } /* Resize l2h back to the actual message length */ - rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg, - mgw_fsm_priv->ranap_rab_ass_resp_oph->msg->l2h, - msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_oph->msg), rc); + rc = msgb_resize_area(mgw_fsm_priv->ranap_rab_ass_resp_msgb, + mgw_fsm_priv->ranap_rab_ass_resp_msgb->l2h, + msgb_l2len(mgw_fsm_priv->ranap_rab_ass_resp_msgb), rc); OSMO_ASSERT(rc == 0); /* When the established state is entered, the modified RAB AssignmentResponse is forwarded to the MSC. @@ -466,14 +465,16 @@ static void mgw_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_ { struct mgw_fsm_priv *mgw_fsm_priv = fi->priv; struct hnbgw_context_map *map = mgw_fsm_priv->map; - struct osmo_prim_hdr *oph = mgw_fsm_priv->ranap_rab_ass_resp_oph; - struct hnb_context *hnb = map->hnb_ctx; - struct hnbgw_cnlink *cn = hnb->gw->sccp.cnlink; + struct msgb *ranap_msg; int rc; LOGPFSML(fi, LOGL_DEBUG, "forwarding modified RAB-AssignmentResponse to MSC\n"); - rc = osmo_sccp_user_sap_down(cn->sccp_user, oph); - mgw_fsm_priv->ranap_rab_ass_resp_oph = NULL; + + ranap_msg = mgw_fsm_priv->ranap_rab_ass_resp_msgb; + mgw_fsm_priv->ranap_rab_ass_resp_msgb = NULL; + talloc_steal(OTC_SELECT, ranap_msg); + + rc = map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg); if (rc < 0) { LOGPFSML(fi, LOGL_DEBUG, "failed to forward RAB-AssignmentResponse message\n"); osmo_fsm_inst_state_chg(fi, MGW_ST_FAILURE, 0, 0); @@ -530,28 +531,6 @@ static int mgw_fsm_timer_cb(struct osmo_fsm_inst *fi) static void mgw_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct mgw_fsm_priv *mgw_fsm_priv = fi->priv; - struct osmo_scu_prim *scu_prim; - struct msgb *scu_msg; - - if (mgw_fsm_priv->ranap_rab_ass_req_message) { - ranap_ran_rx_co_free(mgw_fsm_priv->ranap_rab_ass_req_message); - talloc_free(mgw_fsm_priv->ranap_rab_ass_req_message); - mgw_fsm_priv->ranap_rab_ass_req_message = NULL; - } - - if (mgw_fsm_priv->ranap_rab_ass_resp_message) { - ranap_cn_rx_co_free(mgw_fsm_priv->ranap_rab_ass_resp_message); - talloc_free(mgw_fsm_priv->ranap_rab_ass_resp_message); - mgw_fsm_priv->ranap_rab_ass_resp_message = NULL; - } - - if (mgw_fsm_priv->ranap_rab_ass_resp_oph) { - scu_prim = (struct osmo_scu_prim *)mgw_fsm_priv->ranap_rab_ass_resp_oph; - scu_msg = scu_prim->oph.msg; - msgb_free(scu_msg); - mgw_fsm_priv->ranap_rab_ass_resp_oph = NULL; - } - talloc_free(mgw_fsm_priv); } @@ -654,7 +633,7 @@ static struct osmo_fsm mgw_fsm = { }; /* The MSC may ask to release a specific RAB within a RAB-AssignmentRequest */ -static int handle_rab_release(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +static int handle_rab_release(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message) { bool rab_release_req; struct osmo_fsm_inst *fi = map->mgw_fi; @@ -672,7 +651,7 @@ static int handle_rab_release(struct hnbgw_context_map *map, struct osmo_prim_hd /* Forward the unmodifed RAB-AssignmentRequest to HNB, so that the HNB is informed about the RAB release as * well */ LOGPFSML(fi, LOGL_DEBUG, "forwarding unmodified RAB-AssignmentRequest to HNB\n"); - rc = rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msgb_l2(oph->msg), msgb_l2len(oph->msg)); + rc = map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg); /* Release the FSM normally */ osmo_fsm_inst_state_chg(fi, MGW_ST_RELEASE, 0, 0); @@ -680,12 +659,14 @@ static int handle_rab_release(struct hnbgw_context_map *map, struct osmo_prim_hd return rc; } -/*! Allocate MGW FSM and handle RANAP RAB AssignmentRequest). - * \ptmap[in] map hanbgw context map that is responsible for this call. - * \ptmap[in] oph osmo prim header with RANAP RAB AssignmentResponse (function takes no ownership). - * \ptmap[in] message ranap message container (function takes ownership). +/*! Allocate MGW FSM and handle RANAP RAB AssignmentRequest. + * \param[in] map hnbgw context map that is responsible for this call. + * \param[in] ranap_msg msgb containing RANAP RAB AssignmentRequest at msgb_l2(), allocated in OTC_SELECT. + * This function may talloc_steal(ranap_msg) to keep it for later. + * \param[in] message decoded RANAP message container, allocated in OTC_SELECT. + * This function may talloc_steal(message) to keep it for later. * \returns 0 on success; negative on error. */ -int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +int handle_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message) { static bool initialized = false; struct mgw_fsm_priv *mgw_fsm_priv; @@ -703,7 +684,7 @@ int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, * a ReleaseList. In this case an FSM will already be present. */ if (map->mgw_fi) { /* A RAB Release might be in progress, handle it */ - rc = handle_rab_release(map, oph, message); + rc = handle_rab_release(map, ranap_msg, message); if (rc >= 0) return rc; @@ -726,6 +707,8 @@ int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, mgw_fsm_priv = talloc_zero(map, struct mgw_fsm_priv); mgw_fsm_priv->map = map; + + talloc_steal(mgw_fsm_priv, message); mgw_fsm_priv->ranap_rab_ass_req_message = message; /* Allocate FSM */ @@ -738,17 +721,17 @@ int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, } /*! Handlie RANAP RAB AssignmentResponse (deliver message, complete RTP stream switching). - * \ptmap[in] map hanbgw context map that is responsible for this call. - * \ptmap[in] oph osmo prim header with RANAP RAB AssignmentResponse (function takes ownership). - * \ptmap[in] message ranap message container with decoded ranap message (function takes ownership). + * \param[in] map hnbgw context map that is responsible for this call. + * \param[in] ranap_msg msgb containing RANAP RAB AssignmentResponse at msgb_l2(), allocated in OTC_SELECT. + * This function may talloc_steal(ranap_msg) to keep it for later. + * \param[in] message decoded RANAP message container, allocated in OTC_SELECT. + * This function may talloc_steal(message) to keep it for later. * \returns 0 on success; negative on error. */ -int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message) { struct mgw_fsm_priv *mgw_fsm_priv; - struct osmo_scu_prim *prim; - struct msgb *msg; - OSMO_ASSERT(oph); + OSMO_ASSERT(ranap_msg); if (!map->mgw_fi) { /* NOTE: This situation is a corner-case. We may end up here when the co-located MGW caused a problem @@ -758,23 +741,19 @@ int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_ "mgw_fsm_handle_rab_ass_resp() rua_ctx_id=%d, no MGW fsm -- sending Iu-Release-Request!\n", map->rua_ctx_id); - /* Cleanup ranap message */ - ranap_cn_rx_co_free(message); - talloc_free(message); - - /* Toss RAB-AssignmentResponse */ - prim = (struct osmo_scu_prim *)oph; - msg = prim->oph.msg; - msgb_free(msg); - /* Send a release request, to make sure that the MSC is aware of the problem. */ tx_release_req(map); return -1; } mgw_fsm_priv = map->mgw_fi->priv; - mgw_fsm_priv->ranap_rab_ass_resp_oph = oph; + + talloc_steal(mgw_fsm_priv, ranap_msg); + mgw_fsm_priv->ranap_rab_ass_resp_msgb = ranap_msg; + + talloc_steal(mgw_fsm_priv, message); mgw_fsm_priv->ranap_rab_ass_resp_message = message; + osmo_fsm_inst_dispatch(map->mgw_fi, MGW_EV_RAB_ASS_RESP, NULL); return 0; } diff --git a/src/osmo-hnbgw/ps_rab_ass_fsm.c b/src/osmo-hnbgw/ps_rab_ass_fsm.c index e232de8..65a6116 100644 --- a/src/osmo-hnbgw/ps_rab_ass_fsm.c +++ b/src/osmo-hnbgw/ps_rab_ass_fsm.c @@ -110,7 +110,7 @@ struct ps_rab_ass { ranap_message *ranap_rab_ass_req_message; ranap_message *ranap_rab_ass_resp_message; - struct osmo_prim_hdr *ranap_rab_ass_resp_oph; + struct msgb *ranap_rab_ass_resp_msgb; /* A RAB Assignment may contain more than one RAB. Each RAB sets up a distinct ps_rab_fsm (aka PFCP session) and * reports back about local F-TEIDs assigned by the UPF. This gives the nr of RAB events we expect from @@ -214,16 +214,25 @@ error_exit: return rc; } -int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +/*! Allocate ps_rab_ass_fsm and handle PS RANAP RAB AssignmentRequest. + * \param[in] map hnbgw context map that is responsible for this conn. + * \param[in] ranap_msg msgb containing RANAP RAB AssignmentRequest at msgb_l2(), allocated in OTC_SELECT. + * This function may talloc_steal(ranap_msg) to keep it for later. + * \param[in] message decoded RANAP message container, allocated in OTC_SELECT. + * This function may talloc_steal(message) to keep it for later. + * \returns 0 on success; negative on error. */ +int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message) { RANAP_RAB_AssignmentRequestIEs_t *ies = &message->msg.raB_AssignmentRequestIEs; int i; - struct hnb_gw *hnb_gw = map->hnb_ctx->gw; + struct hnb_gw *hnb_gw = map->gw; struct ps_rab_ass *rab_ass; struct osmo_fsm_inst *fi; rab_ass = ps_rab_ass_alloc(map); + + talloc_steal(rab_ass, message); rab_ass->ranap_rab_ass_req_message = message; /* Now rab_ass owns message and will clean it up */ @@ -351,8 +360,10 @@ continue_cleanloop: ps_rab_ass_failure(rab_ass); return; } - rua_tx_dt(rab_ass->map->hnb_ctx, rab_ass->map->is_ps, rab_ass->map->rua_ctx_id, msg->data, msg->len); - msgb_free(msg); + talloc_steal(OTC_SELECT, msg); + msg->l2h = msg->data; + map_rua_dispatch(rab->map, MAP_RUA_EV_TX_DIRECT_TRANSFER, msg); + /* The request message has been forwarded. The response will be handled by a new FSM instance. * We are done. */ osmo_fsm_inst_term(rab_ass->fi, OSMO_FSM_TERM_REGULAR, NULL); @@ -385,7 +396,7 @@ static int ps_rab_setup_access_remote(struct ps_rab_ass *rab_ass, return ps_rab_rx_access_remote_f_teid(map, rab_id, &args); } -int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message) +int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message) { /* hNodeB responds with its own F-TEIDs. Need to tell the UPF about those to complete the GTP mapping. * 1. here, extract the F-TEIDs (one per RAB), @@ -408,7 +419,7 @@ int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim struct ps_rab_ass *rab_ass; struct osmo_fsm_inst *fi; RANAP_RAB_AssignmentResponseIEs_t *ies; - struct hnb_gw *hnb_gw = map->hnb_ctx->gw; + struct hnb_gw *hnb_gw = map->gw; /* Make sure we indeed deal with a setup-or-modify list */ ies = &message->msg.raB_AssignmentResponseIEs; @@ -418,8 +429,12 @@ int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim } rab_ass = ps_rab_ass_alloc(map); + + talloc_steal(rab_ass, message); rab_ass->ranap_rab_ass_resp_message = message; - rab_ass->ranap_rab_ass_resp_oph = oph; + + talloc_steal(rab_ass, ranap_msg); + rab_ass->ranap_rab_ass_resp_msgb = ranap_msg; /* Now rab_ass owns message and will clean it up */ if (!osmo_pfcp_cp_peer_is_associated(hnb_gw->pfcp.cp_peer)) { @@ -494,8 +509,8 @@ static void ps_rab_ass_resp_send_if_ready(struct ps_rab_ass *rab_ass) { int i; int rc; - struct hnbgw_cnlink *cn = rab_ass->map->cn_link; RANAP_RAB_AssignmentResponseIEs_t *ies = &rab_ass->ranap_rab_ass_resp_message->msg.raB_AssignmentResponseIEs; + struct msgb *msg; /* Go through all RABs in the RAB Assignment Response message and replace with the F-TEID that the UPF assigned, * verifying that instructing the UPF has succeeded. */ @@ -565,10 +580,13 @@ continue_cleanloop: ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_RANAP_RAB_SetupOrModifiedItem, &item_ies); } + msg = rab_ass->ranap_rab_ass_resp_msgb; + rab_ass->ranap_rab_ass_resp_msgb = NULL; + talloc_steal(OTC_SELECT, msg); + /* Replaced all the GTP info, re-encode the message. Since we are replacing data 1:1, taking care to use the * same IP address encoding, the resulting message size must be identical to the original message size. */ - rc = ranap_rab_ass_resp_encode(msgb_l2(rab_ass->ranap_rab_ass_resp_oph->msg), - msgb_l2len(rab_ass->ranap_rab_ass_resp_oph->msg), ies); + rc = ranap_rab_ass_resp_encode(msgb_l2(msg), msgb_l2len(msg), ies); if (rc < 0) { LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Re-encoding RANAP PS RAB-AssignmentResponse failed\n"); ps_rab_ass_failure(rab_ass); @@ -576,8 +594,8 @@ continue_cleanloop: } LOG_PS_RAB_ASS(rab_ass, LOGL_NOTICE, "Sending RANAP PS RAB-AssignmentResponse with mapped GTP info\n"); - rc = osmo_sccp_user_sap_down(cn->sccp_user, rab_ass->ranap_rab_ass_resp_oph); - rab_ass->ranap_rab_ass_resp_oph = NULL; + + rc = map_sccp_dispatch(rab_ass->map, MAP_SCCP_EV_TX_DATA_REQUEST, msg); if (rc < 0) { LOG_PS_RAB_ASS(rab_ass, LOGL_ERROR, "Sending RANAP PS RAB-AssignmentResponse failed\n"); ps_rab_ass_failure(rab_ass); @@ -590,29 +608,8 @@ continue_cleanloop: static void ps_rab_ass_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct ps_rab_ass *rab_ass = fi->priv; - struct osmo_scu_prim *scu_prim; - struct msgb *scu_msg; struct ps_rab *rab; - if (rab_ass->ranap_rab_ass_req_message) { - ranap_ran_rx_co_free(rab_ass->ranap_rab_ass_req_message); - talloc_free(rab_ass->ranap_rab_ass_req_message); - rab_ass->ranap_rab_ass_req_message = NULL; - } - - if (rab_ass->ranap_rab_ass_resp_message) { - ranap_cn_rx_co_free(rab_ass->ranap_rab_ass_resp_message); - talloc_free(rab_ass->ranap_rab_ass_resp_message); - rab_ass->ranap_rab_ass_resp_message = NULL; - } - - if (rab_ass->ranap_rab_ass_resp_oph) { - scu_prim = (struct osmo_scu_prim *)rab_ass->ranap_rab_ass_resp_oph; - scu_msg = scu_prim->oph.msg; - msgb_free(scu_msg); - rab_ass->ranap_rab_ass_resp_oph = NULL; - } - llist_for_each_entry(rab, &rab_ass->map->ps_rabs, entry) { if (rab->req_fi == fi) rab->req_fi = NULL; diff --git a/src/osmo-hnbgw/ps_rab_fsm.c b/src/osmo-hnbgw/ps_rab_fsm.c index dcc18ca..f4a1f12 100644 --- a/src/osmo-hnbgw/ps_rab_fsm.c +++ b/src/osmo-hnbgw/ps_rab_fsm.c @@ -87,7 +87,7 @@ static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id /* Allocate with the global hnb_gw, so that we can gracefully handle PFCP release even if a hnb_ctx gets * deallocated. */ - fi = osmo_fsm_inst_alloc(&ps_rab_fsm, map->hnb_ctx->gw, NULL, LOGL_DEBUG, NULL); + fi = osmo_fsm_inst_alloc(&ps_rab_fsm, map->gw, NULL, LOGL_DEBUG, NULL); OSMO_ASSERT(fi); osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u-RAB-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id, rab_id); @@ -96,7 +96,7 @@ static struct ps_rab *ps_rab_alloc(struct hnbgw_context_map *map, uint8_t rab_id OSMO_ASSERT(rab); *rab = (struct ps_rab){ .fi = fi, - .hnb_gw = map->hnb_ctx->gw, + .hnb_gw = map->gw, .map = map, .rab_id = rab_id, .use_count = { @@ -683,6 +683,7 @@ static void ps_rab_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, static void ps_rab_forget_map(struct ps_rab *rab) { + /* remove from map->ps_rabs */ if (rab->map) llist_del(&rab->entry); rab->map = NULL; diff --git a/src/osmo-hnbgw/tdefs.c b/src/osmo-hnbgw/tdefs.c index d798345..7af292d 100644 --- a/src/osmo-hnbgw/tdefs.c +++ b/src/osmo-hnbgw/tdefs.c @@ -36,9 +36,15 @@ struct osmo_tdef ps_T_defs[] = { { } }; +struct osmo_tdef cmap_T_defs[] = { + {.T = -31, .default_val = 5, .desc = "Timeout for discarding a partially released context map (RUA <-> SCCP)" }, + { } +}; + struct osmo_tdef_group hnbgw_tdef_group[] = { {.name = "mgw", .tdefs = mgw_fsm_T_defs, .desc = "MGW (Media Gateway) interface" }, {.name = "ps", .tdefs = ps_T_defs, .desc = "timers for Packet Switched domain" }, + {.name = "cmap", .tdefs = cmap_T_defs, .desc = "timers for context maps (RUA <-> SCCP)" }, #if ENABLE_PFCP {.name = "pfcp", .tdefs = osmo_pfcp_tdefs, .desc = "PFCP timers" }, #endif