pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-hnbgw/+/40243?usp=email )
Change subject: Move SCCP prim handling to its own hnbgw_sccp.{c,h} file ...................................................................... Move SCCP prim handling to its own hnbgw_sccp.{c,h} file Similar to what we already have for other protocols; helps properly splitting logic per layer. Change-Id: Idb45a25f7ff4bcd38ffd27bf1d3360b9d34149b3 --- M include/osmocom/hnbgw/Makefile.am M include/osmocom/hnbgw/hnbgw.h M include/osmocom/hnbgw/hnbgw_cn.h A include/osmocom/hnbgw/hnbgw_sccp.h M src/osmo-hnbgw/Makefile.am M src/osmo-hnbgw/hnbgw_cn.c A src/osmo-hnbgw/hnbgw_sccp.c 7 files changed, 432 insertions(+), 376 deletions(-) Approvals: laforge: Looks good to me, but someone else must approve osmith: Looks good to me, approved Jenkins Builder: Verified diff --git a/include/osmocom/hnbgw/Makefile.am b/include/osmocom/hnbgw/Makefile.am index 03821d5..d3fbb07 100644 --- a/include/osmocom/hnbgw/Makefile.am +++ b/include/osmocom/hnbgw/Makefile.am @@ -6,6 +6,7 @@ hnbgw_pfcp.h \ hnbgw_ranap.h \ hnbgw_rua.h \ + hnbgw_sccp.h \ kpi.h \ mgw_fsm.h \ nft_kpi.h \ diff --git a/include/osmocom/hnbgw/hnbgw.h b/include/osmocom/hnbgw/hnbgw.h index 23857d5..3995294 100644 --- a/include/osmocom/hnbgw/hnbgw.h +++ b/include/osmocom/hnbgw/hnbgw.h @@ -8,7 +8,6 @@ #include <osmocom/core/rate_ctr.h> #include <osmocom/core/sockaddr_str.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> @@ -20,6 +19,7 @@ #include <osmocom/mgcp_client/mgcp_client_pool.h> #include <osmocom/hnbgw/nft_kpi.h> +#include <osmocom/hnbgw/hnbgw_sccp.h> #define STORE_UPTIME_INTERVAL 10 /* seconds */ #define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */ @@ -202,32 +202,6 @@ 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; diff --git a/include/osmocom/hnbgw/hnbgw_cn.h b/include/osmocom/hnbgw/hnbgw_cn.h index a5fa132..78b82b7 100644 --- a/include/osmocom/hnbgw/hnbgw_cn.h +++ b/include/osmocom/hnbgw/hnbgw_cn.h @@ -8,8 +8,6 @@ #include <osmocom/hnbgw/hnbgw.h> struct hnbgw_cnlink *cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr); -struct hnbgw_cnlink *hnbgw_cnlink_find_by_addr(const struct hnbgw_sccp_user *hsu, - const struct osmo_sccp_addr *remote_addr); struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map); void hnbgw_cnpool_start(struct hnbgw_cnpool *cnpool); diff --git a/include/osmocom/hnbgw/hnbgw_sccp.h b/include/osmocom/hnbgw/hnbgw_sccp.h new file mode 100644 index 0000000..8188b55 --- /dev/null +++ b/include/osmocom/hnbgw/hnbgw_sccp.h @@ -0,0 +1,38 @@ +/* SCCP, ITU Q.711 - Q.714 */ +#pragma once + +#include <osmocom/core/hashtable.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/prim.h> + +#include <osmocom/sigtran/sccp_sap.h> + +struct hnbgw_cnlink; + +/* 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) + +struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(const struct hnbgw_cnlink *cnlink, int ss7_inst_id); diff --git a/src/osmo-hnbgw/Makefile.am b/src/osmo-hnbgw/Makefile.am index e108906..df2bb23 100644 --- a/src/osmo-hnbgw/Makefile.am +++ b/src/osmo-hnbgw/Makefile.am @@ -37,6 +37,7 @@ hnbgw_l3.c \ hnbgw_rua.c \ hnbgw_ranap.c \ + hnbgw_sccp.c \ hnbgw_vty.c \ context_map.c \ context_map_rua.c \ diff --git a/src/osmo-hnbgw/hnbgw_cn.c b/src/osmo-hnbgw/hnbgw_cn.c index 49ae9ae..7b5c575 100644 --- a/src/osmo-hnbgw/hnbgw_cn.c +++ b/src/osmo-hnbgw/hnbgw_cn.c @@ -37,7 +37,7 @@ #include <osmocom/sigtran/sccp_helpers.h> #include <osmocom/hnbgw/hnbgw.h> -#include <osmocom/hnbgw/hnbgw_rua.h> +#include <osmocom/hnbgw/hnbgw_sccp.h> #include <osmocom/hnbgw/hnbgw_ranap.h> #include <osmocom/hnbgw/hnbgw_cn.h> #include <osmocom/hnbgw/context_map.h> @@ -46,273 +46,6 @@ * Incoming primitives from SCCP User SAP ***********************************************************************/ -static struct hnbgw_cnlink *cnlink_from_addr(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *calling_addr, - const struct osmo_prim_hdr *oph) -{ - struct hnbgw_cnlink *cnlink = NULL; - cnlink = hnbgw_cnlink_find_by_addr(hsu, calling_addr); - if (!cnlink) { - LOG_HSI(hsu, DRANAP, LOGL_ERROR, "Rx from unknown SCCP peer: %s: %s\n", - osmo_sccp_inst_addr_name(osmo_ss7_get_sccp(hsu->ss7), calling_addr), - osmo_scu_prim_hdr_name_c(OTC_SELECT, oph)); - return NULL; - } - return cnlink; -} - -static struct hnbgw_context_map *map_from_conn_id(struct hnbgw_sccp_user *hsu, uint32_t conn_id, - const struct osmo_prim_hdr *oph) -{ - struct hnbgw_context_map *map; - hash_for_each_possible(hsu->hnbgw_context_map_by_conn_id, map, hnbgw_sccp_user_entry, conn_id) { - if (map->scu_conn_id == conn_id) - return map; - } - LOGP(DRANAP, LOGL_ERROR, "Rx for unknown SCCP connection ID: %u: %s\n", - conn_id, osmo_scu_prim_hdr_name_c(OTC_SELECT, oph)); - return NULL; -} - -static int handle_cn_unitdata(struct hnbgw_sccp_user *hsu, - const struct osmo_scu_unitdata_param *param, - struct osmo_prim_hdr *oph) -{ - struct hnbgw_cnlink *cnlink = cnlink_from_addr(hsu, ¶m->calling_addr, oph); - if (!cnlink) - return -ENOENT; - - if (param->called_addr.ssn != OSMO_SCCP_SSN_RANAP) { - LOGP(DCN, LOGL_NOTICE, "N-UNITDATA.ind for unknown SSN %u\n", - param->called_addr.ssn); - return -1; - } - - return hnbgw_ranap_rx_udt_dl(cnlink, param, msgb_l2(oph->msg), msgb_l2len(oph->msg)); -} - -static int handle_cn_conn_conf(struct hnbgw_sccp_user *hsu, - const struct osmo_scu_connect_param *param, - struct osmo_prim_hdr *oph) -{ - struct hnbgw_context_map *map; - - map = map_from_conn_id(hsu, param->conn_id, oph); - if (!map || !map->cnlink) - return -ENOENT; - - LOGP(DCN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d, addrs: called=%s calling=%s responding=%s\n", - param->conn_id, - cnlink_sccp_addr_to_str(map->cnlink, ¶m->called_addr), - cnlink_sccp_addr_to_str(map->cnlink, ¶m->calling_addr), - cnlink_sccp_addr_to_str(map->cnlink, ¶m->responding_addr)); - - map_sccp_dispatch(map, MAP_SCCP_EV_RX_CONNECTION_CONFIRM, oph->msg); - return 0; -} - -static int handle_cn_data_ind(struct hnbgw_sccp_user *hsu, - const struct osmo_scu_data_param *param, - struct osmo_prim_hdr *oph) -{ - struct hnbgw_context_map *map; - - map = map_from_conn_id(hsu, param->conn_id, oph); - if (!map || !map->cnlink) - return -ENOENT; - - return map_sccp_dispatch(map, MAP_SCCP_EV_RX_DATA_INDICATION, oph->msg); -} - -static int handle_cn_disc_ind(struct hnbgw_sccp_user *hsu, - const struct osmo_scu_disconn_param *param, - struct osmo_prim_hdr *oph) -{ - struct hnbgw_context_map *map; - char cause_buf[128]; - - map = map_from_conn_id(hsu, param->conn_id, oph); - if (!map || !map->cnlink) - return -ENOENT; - - LOGP(DCN, LOGL_DEBUG, "handle_cn_disc_ind() conn_id=%u responding_addr=%s cause=%s\n", - param->conn_id, - cnlink_sccp_addr_to_str(map->cnlink, ¶m->responding_addr), - osmo_sua_sccp_cause_name(param->cause, cause_buf, sizeof(cause_buf))); - - return map_sccp_dispatch(map, MAP_SCCP_EV_RX_RELEASED, oph->msg); -} - -static struct hnbgw_cnlink *_cnlink_find_by_remote_pc(struct hnbgw_cnpool *cnpool, struct osmo_ss7_instance *cs7, uint32_t pc) -{ - struct hnbgw_cnlink *cnlink; - llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) { - if (!cnlink->hnbgw_sccp_user) - continue; - if (cnlink->hnbgw_sccp_user->ss7 != cs7) - continue; - if ((cnlink->remote_addr.presence & OSMO_SCCP_ADDR_T_PC) == 0) - continue; - if (cnlink->remote_addr.pc != pc) - continue; - return cnlink; - } - return NULL; -} - -/* Find a cnlink by its remote sigtran point code on a given cs7 instance. */ -static struct hnbgw_cnlink *cnlink_find_by_remote_pc(struct osmo_ss7_instance *cs7, uint32_t pc) -{ - struct hnbgw_cnlink *cnlink; - cnlink = _cnlink_find_by_remote_pc(&g_hnbgw->sccp.cnpool_iucs, cs7, pc); - if (!cnlink) - cnlink = _cnlink_find_by_remote_pc(&g_hnbgw->sccp.cnpool_iups, cs7, pc); - return cnlink; -} - -static void handle_pcstate_ind(struct hnbgw_sccp_user *hsu, const struct osmo_scu_pcstate_param *pcst) -{ - struct hnbgw_cnlink *cnlink; - bool connected; - bool disconnected; - struct osmo_ss7_instance *cs7 = hsu->ss7; - - LOGP(DCN, LOGL_DEBUG, "N-PCSTATE ind: affected_pc=%u sp_status=%s remote_sccp_status=%s\n", - pcst->affected_pc, osmo_sccp_sp_status_name(pcst->sp_status), - osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); - - /* If we don't care about that point-code, ignore PCSTATE. */ - cnlink = cnlink_find_by_remote_pc(cs7, pcst->affected_pc); - if (!cnlink) - return; - - /* See if this marks the point code to have become available, or to have been lost. - * - * I want to detect two events: - * - connection event (both indicators say PC is reachable). - * - disconnection event (at least one indicator says the PC is not reachable). - * - * There are two separate incoming indicators with various possible values -- the incoming events can be: - * - * - neither connection nor disconnection indicated -- just indicating congestion - * connected == false, disconnected == false --> do nothing. - * - both incoming values indicate that we are connected - * --> trigger connected - * - both indicate we are disconnected - * --> trigger disconnected - * - one value indicates 'connected', the other indicates 'disconnected' - * --> trigger disconnected - * - * Congestion could imply that we're connected, but it does not indicate that a PC's reachability changed, so no need to - * trigger on that. - */ - connected = false; - disconnected = false; - - switch (pcst->sp_status) { - case OSMO_SCCP_SP_S_ACCESSIBLE: - connected = true; - break; - case OSMO_SCCP_SP_S_INACCESSIBLE: - disconnected = true; - break; - default: - case OSMO_SCCP_SP_S_CONGESTED: - /* Neither connecting nor disconnecting */ - break; - } - - switch (pcst->remote_sccp_status) { - case OSMO_SCCP_REM_SCCP_S_AVAILABLE: - if (!disconnected) - connected = true; - break; - case OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN: - case OSMO_SCCP_REM_SCCP_S_UNEQUIPPED: - case OSMO_SCCP_REM_SCCP_S_INACCESSIBLE: - disconnected = true; - connected = false; - break; - default: - case OSMO_SCCP_REM_SCCP_S_CONGESTED: - /* Neither connecting nor disconnecting */ - break; - } - - if (disconnected && cnlink_is_conn_ready(cnlink)) { - LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, - "now unreachable: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n", - pcst->affected_pc, - osmo_sccp_sp_status_name(pcst->sp_status), - osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); - /* A previously usable cnlink has disconnected. Kick it back to DISC state. */ - cnlink_set_disconnected(cnlink); - } else if (connected && !cnlink_is_conn_ready(cnlink)) { - LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, - "now available: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n", - pcst->affected_pc, - osmo_sccp_sp_status_name(pcst->sp_status), - osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); - /* A previously unusable cnlink has become reachable. Trigger immediate RANAP RESET -- we would resend a - * RESET either way, but we might as well do it now to speed up connecting. */ - cnlink_resend_reset(cnlink); - } -} - -/* Entry point for primitives coming up from SCCP User SAP. - * Ownership of oph->msg is transferred to us. */ -static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx) -{ - struct osmo_sccp_user *scu = ctx; - struct hnbgw_sccp_user *hsu; - struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; - int rc = 0; - - LOGP(DCN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph)); - - if (!scu) { - LOGP(DCN, LOGL_ERROR, - "sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n", - oph->sap, oph->primitive, oph->operation); - return -1; - } - - hsu = osmo_sccp_user_get_priv(scu); - if (!hsu) { - LOGP(DCN, LOGL_ERROR, - "sccp_sap_up(): NULL hnbgw_sccp_user, cannot send prim (sap %u prim %u op %d)\n", - oph->sap, oph->primitive, oph->operation); - 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(hsu, &prim->u.unitdata, oph); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): - rc = handle_cn_conn_conf(hsu, &prim->u.connect, oph); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): - rc = handle_cn_data_ind(hsu, &prim->u.data, oph); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): - rc = handle_cn_disc_ind(hsu, &prim->u.disconnect, oph); - break; - case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION): - handle_pcstate_ind(hsu, &prim->u.pcstate); - break; - - default: - LOGP(DCN, LOGL_ERROR, - "Received unknown prim %u from SCCP USER SAP\n", - OSMO_PRIM_HDR(oph)); - break; - } - - return rc; -} - static bool addr_has_pc_and_ssn(const struct osmo_sccp_addr *addr) { if (!(addr->presence & OSMO_SCCP_ADDR_T_SSN)) @@ -423,61 +156,6 @@ cnlink->name, cnlink->use.remote_addr_name ? : "(default remote point-code)"); } -static struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(const struct hnbgw_cnlink *cnlink, int ss7_id) -{ - struct osmo_ss7_instance *ss7 = NULL; - struct osmo_sccp_instance *sccp; - struct osmo_sccp_user *sccp_user; - uint32_t local_pc; - struct hnbgw_sccp_user *hsu; - - sccp = osmo_sccp_simple_client_on_ss7_id(g_hnbgw, - ss7_id, - cnlink->name, - DEFAULT_PC_HNBGW, - OSMO_SS7_ASP_PROT_M3UA, - 0, - "localhost", - -1, - "localhost"); - if (!sccp) { - LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed to configure SCCP on 'cs7 instance %u'\n", - ss7_id); - return NULL; - } - ss7 = osmo_sccp_get_ss7(sccp); - LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "created SCCP instance on cs7 instance %u\n", osmo_ss7_instance_get_id(ss7)); - - /* Bind the SCCP user, using the cs7 instance's default point-code if one is configured, or osmo-hnbgw's default - * local PC. */ - local_pc = osmo_ss7_instance_get_primary_pc(ss7); - if (!osmo_ss7_pc_is_valid(local_pc)) - local_pc = DEFAULT_PC_HNBGW; - - LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "binding OsmoHNBGW user to cs7 instance %u, local PC %u = %s\n", - osmo_ss7_instance_get_id(ss7), local_pc, osmo_ss7_pointcode_print(ss7, local_pc)); - - sccp_user = osmo_sccp_user_bind_pc(sccp, "OsmoHNBGW", sccp_sap_up, OSMO_SCCP_SSN_RANAP, local_pc); - if (!sccp_user) { - LOGP(DCN, LOGL_ERROR, "Failed to init SCCP User\n"); - return NULL; - } - - hsu = talloc_zero(cnlink, struct hnbgw_sccp_user); - *hsu = (struct hnbgw_sccp_user){ - .name = talloc_asprintf(hsu, "cs7-%u.sccp", osmo_ss7_instance_get_id(ss7)), - .ss7 = ss7, - .sccp_user = sccp_user, - }; - osmo_sccp_make_addr_pc_ssn(&hsu->local_addr, local_pc, OSMO_SCCP_SSN_RANAP); - hash_init(hsu->hnbgw_context_map_by_conn_id); - osmo_sccp_user_set_priv(sccp_user, hsu); - - llist_add_tail(&hsu->entry, &g_hnbgw->sccp.users); - - return hsu; -} - /* If not present yet, set up all of osmo_ss7_instance, osmo_sccp_instance and hnbgw_sccp_user for the given cnlink. * The cs7 instance nr to use is determined by cnlink->remote_addr_name, or cs7 instance 0 if that is not present. * Set cnlink->hnbgw_sccp_user to the new SCCP instance. Return 0 on success, negative on error. */ @@ -581,30 +259,6 @@ return cnlink_alloc(cnpool, nr); } -static bool cnlink_matches(const struct hnbgw_cnlink *cnlink, const struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *remote_addr) -{ - if (cnlink->hnbgw_sccp_user != hsu) - return false; - if (osmo_sccp_addr_cmp(&cnlink->remote_addr, remote_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) - return false; - return true; -} - -struct hnbgw_cnlink *hnbgw_cnlink_find_by_addr(const struct hnbgw_sccp_user *hsu, - const struct osmo_sccp_addr *remote_addr) -{ - struct hnbgw_cnlink *cnlink; - llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iucs.cnlinks, entry) { - if (cnlink_matches(cnlink, hsu, remote_addr)) - return cnlink; - } - llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iups.cnlinks, entry) { - if (cnlink_matches(cnlink, hsu, remote_addr)) - return cnlink; - } - return NULL; -} - static bool is_cnlink_usable(struct hnbgw_cnlink *cnlink, bool is_emerg) { if (is_emerg && !cnlink->allow_emerg) diff --git a/src/osmo-hnbgw/hnbgw_sccp.c b/src/osmo-hnbgw/hnbgw_sccp.c new file mode 100644 index 0000000..8570f51 --- /dev/null +++ b/src/osmo-hnbgw/hnbgw_sccp.c @@ -0,0 +1,390 @@ +/* hnb-gw specific code for SCCP, ITU Q.711 - Q.714 */ + +/* (C) 2015 by Harald Welte <lafo...@gnumonks.org> + * (C) 2025 by sysmocom s.f.m.c. GmbH <i...@sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "config.h" + +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/netif/stream.h> + +#include <osmocom/sigtran/sccp_sap.h> +#include <osmocom/sigtran/sccp_helpers.h> +#include <osmocom/sigtran/protocol/sua.h> + +#include <osmocom/hnbgw/hnbgw_cn.h> +#include <osmocom/hnbgw/context_map.h> +#include <osmocom/hnbgw/hnbgw_ranap.h> + +/*********************************************************************** + * Incoming primitives from SCCP User SAP + ***********************************************************************/ + +static bool cnlink_matches(const struct hnbgw_cnlink *cnlink, const struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *remote_addr) +{ + if (cnlink->hnbgw_sccp_user != hsu) + return false; + if (osmo_sccp_addr_cmp(&cnlink->remote_addr, remote_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) + return false; + return true; +} + +static struct hnbgw_cnlink *hnbgw_cnlink_find_by_addr(const struct hnbgw_sccp_user *hsu, + const struct osmo_sccp_addr *remote_addr) +{ + struct hnbgw_cnlink *cnlink; + llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iucs.cnlinks, entry) { + if (cnlink_matches(cnlink, hsu, remote_addr)) + return cnlink; + } + llist_for_each_entry(cnlink, &g_hnbgw->sccp.cnpool_iups.cnlinks, entry) { + if (cnlink_matches(cnlink, hsu, remote_addr)) + return cnlink; + } + return NULL; +} + +static struct hnbgw_cnlink *cnlink_from_addr(struct hnbgw_sccp_user *hsu, const struct osmo_sccp_addr *calling_addr, + const struct osmo_prim_hdr *oph) +{ + struct hnbgw_cnlink *cnlink = NULL; + cnlink = hnbgw_cnlink_find_by_addr(hsu, calling_addr); + if (!cnlink) { + LOG_HSI(hsu, DRANAP, LOGL_ERROR, "Rx from unknown SCCP peer: %s: %s\n", + osmo_sccp_inst_addr_name(osmo_ss7_get_sccp(hsu->ss7), calling_addr), + osmo_scu_prim_hdr_name_c(OTC_SELECT, oph)); + return NULL; + } + return cnlink; +} + +static struct hnbgw_context_map *map_from_conn_id(struct hnbgw_sccp_user *hsu, uint32_t conn_id, + const struct osmo_prim_hdr *oph) +{ + struct hnbgw_context_map *map; + hash_for_each_possible(hsu->hnbgw_context_map_by_conn_id, map, hnbgw_sccp_user_entry, conn_id) { + if (map->scu_conn_id == conn_id) + return map; + } + LOGP(DRANAP, LOGL_ERROR, "Rx for unknown SCCP connection ID: %u: %s\n", + conn_id, osmo_scu_prim_hdr_name_c(OTC_SELECT, oph)); + return NULL; +} + +static int handle_cn_unitdata(struct hnbgw_sccp_user *hsu, + const struct osmo_scu_unitdata_param *param, + struct osmo_prim_hdr *oph) +{ + struct hnbgw_cnlink *cnlink = cnlink_from_addr(hsu, ¶m->calling_addr, oph); + if (!cnlink) + return -ENOENT; + + if (param->called_addr.ssn != OSMO_SCCP_SSN_RANAP) { + LOGP(DCN, LOGL_NOTICE, "N-UNITDATA.ind for unknown SSN %u\n", + param->called_addr.ssn); + return -1; + } + + return hnbgw_ranap_rx_udt_dl(cnlink, param, msgb_l2(oph->msg), msgb_l2len(oph->msg)); +} + +static int handle_cn_conn_conf(struct hnbgw_sccp_user *hsu, + const struct osmo_scu_connect_param *param, + struct osmo_prim_hdr *oph) +{ + struct hnbgw_context_map *map; + + map = map_from_conn_id(hsu, param->conn_id, oph); + if (!map || !map->cnlink) + return -ENOENT; + + LOGP(DCN, LOGL_DEBUG, "handle_cn_conn_conf() conn_id=%d, addrs: called=%s calling=%s responding=%s\n", + param->conn_id, + cnlink_sccp_addr_to_str(map->cnlink, ¶m->called_addr), + cnlink_sccp_addr_to_str(map->cnlink, ¶m->calling_addr), + cnlink_sccp_addr_to_str(map->cnlink, ¶m->responding_addr)); + + map_sccp_dispatch(map, MAP_SCCP_EV_RX_CONNECTION_CONFIRM, oph->msg); + return 0; +} + +static int handle_cn_data_ind(struct hnbgw_sccp_user *hsu, + const struct osmo_scu_data_param *param, + struct osmo_prim_hdr *oph) +{ + struct hnbgw_context_map *map; + + map = map_from_conn_id(hsu, param->conn_id, oph); + if (!map || !map->cnlink) + return -ENOENT; + + return map_sccp_dispatch(map, MAP_SCCP_EV_RX_DATA_INDICATION, oph->msg); +} + +static int handle_cn_disc_ind(struct hnbgw_sccp_user *hsu, + const struct osmo_scu_disconn_param *param, + struct osmo_prim_hdr *oph) +{ + struct hnbgw_context_map *map; + char cause_buf[128]; + + map = map_from_conn_id(hsu, param->conn_id, oph); + if (!map || !map->cnlink) + return -ENOENT; + + LOGP(DCN, LOGL_DEBUG, "handle_cn_disc_ind() conn_id=%u responding_addr=%s cause=%s\n", + param->conn_id, + cnlink_sccp_addr_to_str(map->cnlink, ¶m->responding_addr), + osmo_sua_sccp_cause_name(param->cause, cause_buf, sizeof(cause_buf))); + + return map_sccp_dispatch(map, MAP_SCCP_EV_RX_RELEASED, oph->msg); +} + +static struct hnbgw_cnlink *_cnlink_find_by_remote_pc(struct hnbgw_cnpool *cnpool, struct osmo_ss7_instance *cs7, uint32_t pc) +{ + struct hnbgw_cnlink *cnlink; + llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) { + if (!cnlink->hnbgw_sccp_user) + continue; + if (cnlink->hnbgw_sccp_user->ss7 != cs7) + continue; + if ((cnlink->remote_addr.presence & OSMO_SCCP_ADDR_T_PC) == 0) + continue; + if (cnlink->remote_addr.pc != pc) + continue; + return cnlink; + } + return NULL; +} + +/* Find a cnlink by its remote sigtran point code on a given cs7 instance. */ +static struct hnbgw_cnlink *cnlink_find_by_remote_pc(struct osmo_ss7_instance *cs7, uint32_t pc) +{ + struct hnbgw_cnlink *cnlink; + cnlink = _cnlink_find_by_remote_pc(&g_hnbgw->sccp.cnpool_iucs, cs7, pc); + if (!cnlink) + cnlink = _cnlink_find_by_remote_pc(&g_hnbgw->sccp.cnpool_iups, cs7, pc); + return cnlink; +} + +static void handle_pcstate_ind(struct hnbgw_sccp_user *hsu, const struct osmo_scu_pcstate_param *pcst) +{ + struct hnbgw_cnlink *cnlink; + bool connected; + bool disconnected; + struct osmo_ss7_instance *cs7 = hsu->ss7; + + LOGP(DCN, LOGL_DEBUG, "N-PCSTATE ind: affected_pc=%u sp_status=%s remote_sccp_status=%s\n", + pcst->affected_pc, osmo_sccp_sp_status_name(pcst->sp_status), + osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); + + /* If we don't care about that point-code, ignore PCSTATE. */ + cnlink = cnlink_find_by_remote_pc(cs7, pcst->affected_pc); + if (!cnlink) + return; + + /* See if this marks the point code to have become available, or to have been lost. + * + * I want to detect two events: + * - connection event (both indicators say PC is reachable). + * - disconnection event (at least one indicator says the PC is not reachable). + * + * There are two separate incoming indicators with various possible values -- the incoming events can be: + * + * - neither connection nor disconnection indicated -- just indicating congestion + * connected == false, disconnected == false --> do nothing. + * - both incoming values indicate that we are connected + * --> trigger connected + * - both indicate we are disconnected + * --> trigger disconnected + * - one value indicates 'connected', the other indicates 'disconnected' + * --> trigger disconnected + * + * Congestion could imply that we're connected, but it does not indicate that a PC's reachability changed, so no need to + * trigger on that. + */ + connected = false; + disconnected = false; + + switch (pcst->sp_status) { + case OSMO_SCCP_SP_S_ACCESSIBLE: + connected = true; + break; + case OSMO_SCCP_SP_S_INACCESSIBLE: + disconnected = true; + break; + default: + case OSMO_SCCP_SP_S_CONGESTED: + /* Neither connecting nor disconnecting */ + break; + } + + switch (pcst->remote_sccp_status) { + case OSMO_SCCP_REM_SCCP_S_AVAILABLE: + if (!disconnected) + connected = true; + break; + case OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN: + case OSMO_SCCP_REM_SCCP_S_UNEQUIPPED: + case OSMO_SCCP_REM_SCCP_S_INACCESSIBLE: + disconnected = true; + connected = false; + break; + default: + case OSMO_SCCP_REM_SCCP_S_CONGESTED: + /* Neither connecting nor disconnecting */ + break; + } + + if (disconnected && cnlink_is_conn_ready(cnlink)) { + LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, + "now unreachable: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n", + pcst->affected_pc, + osmo_sccp_sp_status_name(pcst->sp_status), + osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); + /* A previously usable cnlink has disconnected. Kick it back to DISC state. */ + cnlink_set_disconnected(cnlink); + } else if (connected && !cnlink_is_conn_ready(cnlink)) { + LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, + "now available: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n", + pcst->affected_pc, + osmo_sccp_sp_status_name(pcst->sp_status), + osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status)); + /* A previously unusable cnlink has become reachable. Trigger immediate RANAP RESET -- we would resend a + * RESET either way, but we might as well do it now to speed up connecting. */ + cnlink_resend_reset(cnlink); + } +} + +/* Entry point for primitives coming up from SCCP User SAP. + * Ownership of oph->msg is transferred to us. */ +static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmo_sccp_user *scu = ctx; + struct hnbgw_sccp_user *hsu; + struct osmo_scu_prim *prim = (struct osmo_scu_prim *) oph; + int rc = 0; + + LOGP(DCN, LOGL_DEBUG, "sccp_sap_up(%s)\n", osmo_scu_prim_name(oph)); + + if (!scu) { + LOGP(DCN, LOGL_ERROR, + "sccp_sap_up(): NULL osmo_sccp_user, cannot send prim (sap %u prim %u op %d)\n", + oph->sap, oph->primitive, oph->operation); + return -1; + } + + hsu = osmo_sccp_user_get_priv(scu); + if (!hsu) { + LOGP(DCN, LOGL_ERROR, + "sccp_sap_up(): NULL hnbgw_sccp_user, cannot send prim (sap %u prim %u op %d)\n", + oph->sap, oph->primitive, oph->operation); + 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(hsu, &prim->u.unitdata, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM): + rc = handle_cn_conn_conf(hsu, &prim->u.connect, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION): + rc = handle_cn_data_ind(hsu, &prim->u.data, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION): + rc = handle_cn_disc_ind(hsu, &prim->u.disconnect, oph); + break; + case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION): + handle_pcstate_ind(hsu, &prim->u.pcstate); + break; + + default: + LOGP(DCN, LOGL_ERROR, + "Received unknown prim %u from SCCP USER SAP\n", + OSMO_PRIM_HDR(oph)); + break; + } + + return rc; +} + + +struct hnbgw_sccp_user *hnbgw_sccp_user_alloc(const struct hnbgw_cnlink *cnlink, int ss7_id) +{ + struct osmo_ss7_instance *ss7 = NULL; + struct osmo_sccp_instance *sccp; + struct osmo_sccp_user *sccp_user; + uint32_t local_pc; + struct hnbgw_sccp_user *hsu; + + sccp = osmo_sccp_simple_client_on_ss7_id(g_hnbgw, + ss7_id, + cnlink->name, + DEFAULT_PC_HNBGW, + OSMO_SS7_ASP_PROT_M3UA, + 0, + "localhost", + -1, + "localhost"); + if (!sccp) { + LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Failed to configure SCCP on 'cs7 instance %u'\n", + ss7_id); + return NULL; + } + ss7 = osmo_sccp_get_ss7(sccp); + LOG_CNLINK(cnlink, DCN, LOGL_NOTICE, "created SCCP instance on cs7 instance %u\n", osmo_ss7_instance_get_id(ss7)); + + /* Bind the SCCP user, using the cs7 instance's default point-code if one is configured, or osmo-hnbgw's default + * local PC. */ + local_pc = osmo_ss7_instance_get_primary_pc(ss7); + if (!osmo_ss7_pc_is_valid(local_pc)) + local_pc = DEFAULT_PC_HNBGW; + + LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "binding OsmoHNBGW user to cs7 instance %u, local PC %u = %s\n", + osmo_ss7_instance_get_id(ss7), local_pc, osmo_ss7_pointcode_print(ss7, local_pc)); + + sccp_user = osmo_sccp_user_bind_pc(sccp, "OsmoHNBGW", sccp_sap_up, OSMO_SCCP_SSN_RANAP, local_pc); + if (!sccp_user) { + LOGP(DCN, LOGL_ERROR, "Failed to init SCCP User\n"); + return NULL; + } + + hsu = talloc_zero(cnlink, struct hnbgw_sccp_user); + *hsu = (struct hnbgw_sccp_user){ + .name = talloc_asprintf(hsu, "cs7-%u.sccp", osmo_ss7_instance_get_id(ss7)), + .ss7 = ss7, + .sccp_user = sccp_user, + }; + osmo_sccp_make_addr_pc_ssn(&hsu->local_addr, local_pc, OSMO_SCCP_SSN_RANAP); + hash_init(hsu->hnbgw_context_map_by_conn_id); + osmo_sccp_user_set_priv(sccp_user, hsu); + + llist_add_tail(&hsu->entry, &g_hnbgw->sccp.users); + + return hsu; +} + -- To view, visit https://gerrit.osmocom.org/c/osmo-hnbgw/+/40243?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email Gerrit-MessageType: merged Gerrit-Project: osmo-hnbgw Gerrit-Branch: master Gerrit-Change-Id: Idb45a25f7ff4bcd38ffd27bf1d3360b9d34149b3 Gerrit-Change-Number: 40243 Gerrit-PatchSet: 2 Gerrit-Owner: pespin <pes...@sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: laforge <lafo...@osmocom.org> Gerrit-Reviewer: osmith <osm...@sysmocom.de> Gerrit-Reviewer: pespin <pes...@sysmocom.de>