pespin has uploaded this change for review.

View Change

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(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-hnbgw refs/changes/43/40243/1
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 df8f8e9..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, &param->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, &param->called_addr),
- cnlink_sccp_addr_to_str(map->cnlink, &param->calling_addr),
- cnlink_sccp_addr_to_str(map->cnlink, &param->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, &param->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_inst_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 ? osmo_ss7_instance_get_id(ss7) : 0,
- 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 ? osmo_ss7_instance_get_id(ss7) : 0);
- 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..75e2712
--- /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 <laforge@gnumonks.org>
+ * (C) 2025 by sysmocom s.f.m.c. GmbH <info@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, &param->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, &param->called_addr),
+ cnlink_sccp_addr_to_str(map->cnlink, &param->calling_addr),
+ cnlink_sccp_addr_to_str(map->cnlink, &param->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, &param->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_inst_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 ? osmo_ss7_instance_get_id(ss7) : 0,
+ 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 ? osmo_ss7_instance_get_id(ss7) : 0);
+ 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 change 40243. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-hnbgw
Gerrit-Branch: master
Gerrit-Change-Id: Idb45a25f7ff4bcd38ffd27bf1d3360b9d34149b3
Gerrit-Change-Number: 40243
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin@sysmocom.de>