osmo-hnbgw/include/osmocom/hnbgw/hnbgw.h

478 lines
14 KiB
C
Raw Normal View History

#pragma once
#include <osmocom/core/select.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/sigtran/sccp_sap.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ranap/RANAP_CN-DomainIndicator.h>
#define DEBUG
#include <osmocom/core/logging.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>
#define STORE_UPTIME_INTERVAL 10 /* seconds */
#define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */
enum {
DMAIN,
DHNBAP,
DRUA,
DRANAP,
DMGW,
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
2023-02-12 04:02:48 +00:00
DHNB,
DCN,
};
extern const struct log_info hnbgw_log_info;
extern struct vty_app_info hnbgw_vty_info;
#define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \
LOGP(ss, lvl, "(%s) " fmt, hnb_context_name(HNB_CTX), ## args)
#define DOMAIN_CS RANAP_CN_DomainIndicator_cs_domain
#define DOMAIN_PS RANAP_CN_DomainIndicator_ps_domain
extern const struct value_string ranap_domain_names[];
static inline const char *ranap_domain_name(RANAP_CN_DomainIndicator_t domain)
{
return get_value_string(ranap_domain_names, domain);
}
enum hnb_ctrl_node {
CTRL_NODE_HNB = _LAST_CTRL_NODE,
_LAST_CTRL_NODE_HNB
};
#define HNBGW_LOCAL_IP_DEFAULT "0.0.0.0"
/* TODO: CS and PS now both connect to OsmoSTP, i.e. that's always going to be the same address. Drop the
* duplicity. */
#define HNBGW_IUCS_REMOTE_IP_DEFAULT "127.0.0.1"
#define HNBGW_IUPS_REMOTE_IP_DEFAULT "127.0.0.1"
#define DEFAULT_PC_HNBGW ((23 << 3) + 5)
#define DEFAULT_PC_MSC ((23 << 3) + 1)
#define DEFAULT_PC_SGSN ((23 << 3) + 4)
/* 25.467 Section 7.1 */
#define IUH_DEFAULT_SCTP_PORT 29169
#define RNA_DEFAULT_SCTP_PORT 25471
#define IUH_PPI_RUA 19
#define IUH_PPI_HNBAP 20
#define IUH_PPI_SABP 31
#define IUH_PPI_RNA 42
#define IUH_PPI_PUA 55
#define IUH_MSGB_SIZE 2048
enum hnb_rate_ctr {
HNB_CTR_IUH_ESTABLISHED,
HNB_CTR_RANAP_PS_ERR_IND_UL,
HNB_CTR_RANAP_CS_ERR_IND_UL,
HNB_CTR_RANAP_PS_RESET_REQ_UL,
HNB_CTR_RANAP_CS_RESET_REQ_UL,
HNB_CTR_RANAP_PS_RAB_ACT_REQ,
HNB_CTR_RANAP_CS_RAB_ACT_REQ,
HNB_CTR_RANAP_PS_RAB_ACT_CNF,
HNB_CTR_RANAP_CS_RAB_ACT_CNF,
HNB_CTR_RANAP_PS_RAB_ACT_FAIL,
HNB_CTR_RANAP_CS_RAB_ACT_FAIL,
HNB_CTR_RANAP_PS_RAB_MOD_REQ,
HNB_CTR_RANAP_CS_RAB_MOD_REQ,
HNB_CTR_RANAP_PS_RAB_MOD_CNF,
HNB_CTR_RANAP_CS_RAB_MOD_CNF,
HNB_CTR_RANAP_PS_RAB_MOD_FAIL,
HNB_CTR_RANAP_CS_RAB_MOD_FAIL,
HNB_CTR_RANAP_PS_RAB_REL_REQ,
HNB_CTR_RANAP_CS_RAB_REL_REQ,
HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL,
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL,
HNB_CTR_RANAP_PS_RAB_REL_CNF,
HNB_CTR_RANAP_CS_RAB_REL_CNF,
HNB_CTR_RANAP_PS_RAB_REL_FAIL,
HNB_CTR_RANAP_CS_RAB_REL_FAIL,
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT,
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT,
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL,
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL,
HNB_CTR_RUA_ERR_IND,
HNB_CTR_RUA_PS_CONNECT_UL,
HNB_CTR_RUA_CS_CONNECT_UL,
HNB_CTR_RUA_PS_DISCONNECT_UL,
HNB_CTR_RUA_CS_DISCONNECT_UL,
HNB_CTR_RUA_PS_DISCONNECT_DL,
HNB_CTR_RUA_CS_DISCONNECT_DL,
HNB_CTR_RUA_PS_DT_UL,
HNB_CTR_RUA_CS_DT_UL,
HNB_CTR_RUA_PS_DT_DL,
HNB_CTR_RUA_CS_DT_DL,
HNB_CTR_RUA_UDT_UL,
HNB_CTR_RUA_UDT_DL,
HNB_CTR_PS_PAGING_ATTEMPTED,
HNB_CTR_CS_PAGING_ATTEMPTED,
HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL,
};
enum hnb_stat {
HNB_STAT_UPTIME_SECONDS,
};
struct umts_cell_id {
uint16_t mcc; /*!< Mobile Country Code (0-999) */
uint16_t mnc; /*!< Mobile Network Code (0-999) */
uint16_t lac; /*!< Locaton Area Code (1-65534) */
uint16_t rac; /*!< Routing Area Code (0-255) */
uint16_t sac; /*!< Service Area Code */
uint32_t cid; /*!< Cell ID */
};
const char *umts_cell_id_name(const struct umts_cell_id *ucid);
int umts_cell_id_from_str(struct umts_cell_id *ucid, const char *instr);
/*! are both given umts_cell_id euqal? */
static inline bool umts_cell_id_equal(const struct umts_cell_id *a, const struct umts_cell_id *b)
{
if (a->mcc != b->mcc)
return false;
if (a->mnc != b->mnc)
return false;
if (a->lac != b->lac)
return false;
if (a->rac != b->rac)
return false;
if (a->sac != b->sac)
return false;
if (a->cid != b->cid)
return false;
return true;
}
struct hnbgw_context_map;
/* osmo-hnbgw keeps a single hnbgw_sccp_user per osmo_sccp_instance, for the local point-code and SSN == RANAP.
* This relates the (opaque) osmo_sccp_user to osmo-hnbgw's per-ss7 state. */
struct hnbgw_sccp_user {
/* entry in g_hnbgw->sccp.users */
struct llist_head entry;
/* logging context */
char *name;
/* Which 'cs7 instance' is this for? Below sccp_user is registered at the osmo_sccp_instance ss7->sccp. */
struct osmo_ss7_instance *ss7;
/* Local address: cs7 instance's primary PC if present, else the default HNBGW PC; with SSN == RANAP. */
struct osmo_sccp_addr local_addr;
/* osmo_sccp API state for above local address on above ss7 instance. */
struct osmo_sccp_user *sccp_user;
/* Fast access to the hnbgw_context_map responsible for a given SCCP conn_id of the ss7->sccp instance.
* hlist_node: hnbgw_context_map->hnbgw_sccp_user_entry. */
DECLARE_HASHTABLE(hnbgw_context_map_by_conn_id, 6);
};
#define LOG_HSI(HNBGW_SCCP_INST, SUBSYS, LEVEL, FMT, ARGS...) \
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (HNBGW_SCCP_INST) ? (HNBGW_SCCP_INST)->name : "null", ##ARGS)
/* User provided configuration for struct hnbgw_cnpool. */
struct hnbgw_cnpool_cfg {
uint8_t nri_bitlen;
struct osmo_nri_ranges *null_nri_ranges;
};
/* User provided configuration for struct hnbgw_cnlink. */
struct hnbgw_cnlink_cfg {
/* cs7 address book entry to indicate both the remote point-code of the peer, as well as which cs7 instance to
* use. */
char *remote_addr_name;
struct osmo_nri_ranges *nri_ranges;
};
/* Collection of CN peers to distribute UE connections across. MSCs for DOMAIN_CS, SGSNs for DOMAIN_PS. */
struct hnbgw_cnpool {
RANAP_CN_DomainIndicator_t domain;
/* CN pool string used in VTY config and logging, "iucs" or "iups". */
const char *pool_name;
/* CN peer string used in VTY config and logging, "msc" or "sgsn". */
const char *peer_name;
/* What we use as the remote MSC/SGSN point-code if the user does not configure any address. */
uint32_t default_remote_pc;
struct hnbgw_cnpool_cfg vty;
struct hnbgw_cnpool_cfg use;
/* List of struct hnbgw_cnlink */
struct llist_head cnlinks;
unsigned int round_robin_next_nr;
/* Emergency calls potentially select a different set of MSCs, so to not mess up the normal round-robin
* behavior, emergency calls need a separate round-robin counter. */
unsigned int round_robin_next_emerg_nr;
/* rate counter group that child hnbgw_cnlinks should use (points to msc_ctrg_desc or sgsn_ctrg_desc) */
const struct rate_ctr_group_desc *cnlink_ctrg_desc;
/* Running counters for this pool */
struct rate_ctr_group *ctrs;
};
#define CNPOOL_CTR_INC(cnpool, x) rate_ctr_inc2((cnpool)->ctrs, x)
/* A CN peer, like 'msc 0' or 'sgsn 23' */
struct hnbgw_cnlink {
struct llist_head entry;
/* backpointer to CS or PS CN pool. */
struct hnbgw_cnpool *pool;
struct osmo_fsm_inst *fi;
int nr;
struct hnbgw_cnlink_cfg vty;
struct hnbgw_cnlink_cfg use;
/* To print in logging/VTY */
char *name;
/* Copy of the address book entry use.remote_addr_name. */
struct osmo_sccp_addr remote_addr;
/* The SCCP instance for the cs7 instance indicated by remote_addr_name. (Multiple hnbgw_cnlinks may use the
* same hnbgw_sccp_user -- there is exactly one hnbgw_sccp_user per configured cs7 instance.) */
struct hnbgw_sccp_user *hnbgw_sccp_user;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
bool allow_attach;
bool allow_emerg;
struct llist_head paging;
struct rate_ctr_group *ctrs;
};
#define LOG_CNLINK(CNLINK, SUBSYS, LEVEL, FMT, ARGS...) \
LOGP(SUBSYS, LEVEL, "(%s) " FMT, (CNLINK) ? (CNLINK)->name : "null", ##ARGS)
#define CNLINK_CTR_INC(cnlink, x) rate_ctr_inc2((cnlink)->ctrs, x)
struct hnbgw_cnlink *cnlink_get_nr(struct hnbgw_cnpool *cnpool, int nr, bool create_if_missing);
static inline bool cnlink_is_cs(const struct hnbgw_cnlink *cnlink)
{
return cnlink && cnlink->pool->domain == DOMAIN_CS;
}
static inline bool cnlink_is_ps(const struct hnbgw_cnlink *cnlink)
{
return cnlink && cnlink->pool->domain == DOMAIN_PS;
}
static inline struct osmo_sccp_instance *cnlink_sccp(const struct hnbgw_cnlink *cnlink)
{
if (!cnlink)
return NULL;
if (!cnlink->hnbgw_sccp_user)
return NULL;
if (!cnlink->hnbgw_sccp_user->ss7)
return NULL;
return cnlink->hnbgw_sccp_user->ss7->sccp;
}
/* The lifecycle of the hnb_context object is the same as its conn */
struct hnb_context {
/*! Entry in HNB-global list of HNB */
struct llist_head list;
/*! SCTP socket + write queue for Iuh to this specific HNB */
struct osmo_stream_srv *conn;
/*! copied from HNB-Identity-Info IE */
char identity_info[256];
/*! copied from Cell Identity IE */
struct umts_cell_id id;
/*! SCTP stream ID for HNBAP */
uint16_t hnbap_stream;
/*! SCTP stream ID for RUA */
uint16_t rua_stream;
/*! True if a HNB-REGISTER-REQ from this HNB has been accepted. */
bool hnb_registered;
/* linked list of hnbgw_context_map */
struct llist_head map_list;
/*! pointer to the associated hnb persistent state. Always present after HNB-Register */
struct hnb_persistent *persistent;
};
#define HNBP_CTR(hnbp, x) rate_ctr_group_get_ctr((hnbp)->ctrs, x)
#define HNBP_CTR_INC(hnbp, x) rate_ctr_inc(HNBP_CTR(hnbp, x))
#define HNBP_CTR_ADD(hnbp, x, y) rate_ctr_add2((hnbp)->ctrs, x, y)
#define HNBP_STAT(hbp, x) osmo_stat_item_group_get_item((hnbp)->statg, x)
#define HNBP_STAT_SET(hnbp, x, val) osmo_stat_item_set(HNBP_STAT(hnbp, x), val)
/* persistent data for one HNB. This continues to exist even as conn / hnb_context is deleted on disconnect */
struct hnb_persistent {
/*! Entry in HNBGW-global list of hnb_persistent */
struct llist_head list;
/*! back-pointer to hnb_context. Can be NULL if no context at this point */
struct hnb_context *ctx;
/*! unique cell identity; copied from HNB REGISTER REQ */
struct umts_cell_id id;
/*! stringified version of the cell identiy above (for printing/naming) */
const char *id_str;
/*! copied from HNB-Identity-Info IE */
time_t updowntime;
struct rate_ctr_group *ctrs;
struct osmo_stat_item_group *statg;
};
struct ue_context {
/*! Entry in the HNB-global list of UE */
struct llist_head list;
/*! Unique Context ID for this UE */
uint32_t context_id;
char imsi[16+1];
uint32_t tmsi;
/*! UE is serviced via this HNB */
struct hnb_context *hnb;
};
struct hnbgw {
struct {
const char *iuh_local_ip;
/*! SCTP port for Iuh listening */
uint16_t iuh_local_port;
/*! The UDP port where we receive multiplexed CS user
* plane traffic from HNBs */
uint16_t iuh_cs_mux_port;
struct osmo_plmn_id plmn;
uint16_t rnc_id;
bool hnbap_allow_tmsi;
/*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */
bool log_prefix_hnb_id;
bool accept_all_hnb;
struct mgcp_client_conf *mgcp_client;
struct {
char *local_addr;
uint16_t local_port;
char *remote_addr;
uint16_t remote_port;
struct {
char *access;
char *core;
} netinst;
} pfcp;
} config;
/*! SCTP listen socket for incoming connections */
struct osmo_stream_srv_link *iuh;
/* list of struct hnb_context */
struct llist_head hnb_list;
/* list of struct hnb_persistent */
struct llist_head hnb_persistent_list;
struct osmo_timer_list store_uptime_timer;
/* list of struct ue_context */
struct llist_head ue_list;
/* next availble UE Context ID */
uint32_t next_ue_ctx_id;
struct ctrl_handle *ctrl;
/* currently active CN links for CS and PS */
struct {
/* List of hnbgw_sccp_user */
struct llist_head users;
/* Pool of core network peers: MSCs for IuCS */
struct hnbgw_cnpool cnpool_iucs;
/* Pool of core network peers: SGSNs for IuPS */
struct hnbgw_cnpool cnpool_iups;
} sccp;
/* MGW pool, also includes the single MGCP client as fallback if no
* pool is configured. */
struct mgcp_client_pool *mgw_pool;
struct {
struct osmo_pfcp_endpoint *ep;
struct osmo_pfcp_cp_peer *cp_peer;
} pfcp;
struct osmo_timer_list hnb_store_rab_durations_timer;
};
extern struct hnbgw *g_hnbgw;
extern void *talloc_asn1_ctx;
void g_hnbgw_alloc(void *ctx);
int hnbgw_rua_accept_cb(struct osmo_stream_srv_link *srv, int fd);
int hnb_ctrl_cmds_install(void);
int hnb_ctrl_node_lookup(void *data, vector vline, int *node_type, void **node_data, int *i);
int hnbgw_mgw_setup(void);
struct hnb_context *hnb_context_by_identity_info(const char *identity_info);
const char *hnb_context_name(struct hnb_context *ctx);
struct ue_context *ue_context_by_id(uint32_t id);
struct ue_context *ue_context_by_imsi(const char *imsi);
struct ue_context *ue_context_by_tmsi(uint32_t tmsi);
struct ue_context *ue_context_alloc(struct hnb_context *hnb, const char *imsi,
uint32_t tmsi);
void ue_context_free(struct ue_context *ue);
void hnb_context_release(struct hnb_context *ctx);
void hnb_context_release_ue_state(struct hnb_context *ctx);
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id);
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id);
void hnb_persistent_free(struct hnb_persistent *hnbp);
void hnbgw_vty_init(void);
int hnbgw_vty_go_parent(struct vty *vty);
/* Return true when the user configured GTP mapping to be enabled, by configuring a PFCP link to a UPF.
* Return false when the user configured to skip GTP mapping and RANAP PS RAB Requests/Responses should be passed thru
* 1:1.
* GTP mapping means that there are two GTP tunnels, one towards HNB and one towards CN, and we forward payloads between
* the two tunnels, mapping the TEIDs and GTP addresses. */
static inline bool hnb_gw_is_gtp_mapping_enabled(void)
{
return g_hnbgw->config.pfcp.remote_addr != NULL;
}
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
2023-02-12 04:02:48 +00:00
struct msgb *hnbgw_ranap_msg_alloc(const char *name);
int hnbgw_peek_l3_ul(struct hnbgw_context_map *map, struct msgb *ranap_msg);
unsigned long long hnb_get_updowntime(const struct hnb_context *ctx);