cnpool: return Paging Resp to the exact CN link that Paged

Implement Paging record, remembering which CN link paged for which
mobile identity, for a set time period roundabout 15 seconds.

When receiving a Paging Response from a UE, connect it to the CN link
that sent the Paging.

Related: SYS#6412
Change-Id: I907dbcaeb442ca5630146f8cad40601c448fc40e
This commit is contained in:
Neels Hofmeyr 2023-05-09 04:23:12 +02:00
parent 644d985ba5
commit 1680e560c2
4 changed files with 331 additions and 9 deletions

View File

@ -180,6 +180,7 @@ struct hnbgw_cnlink {
bool allow_attach;
bool allow_emerg;
struct llist_head paging;
struct rate_ctr_group *ctrs;
};

View File

@ -87,6 +87,7 @@ struct hnbgw_cnlink *cnlink_alloc(struct hnbgw_cnpool *cnpool, int nr)
};
talloc_steal(cnlink, name);
INIT_LLIST_HEAD(&cnlink->map_list);
INIT_LLIST_HEAD(&cnlink->paging);
llist_add_tail(&cnlink->entry, &cnpool->cnlinks);
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "allocated\n");

View File

@ -23,6 +23,8 @@
#include <arpa/inet.h>
#include <errno.h>
#include <asn1c/asn1helpers.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/timer.h>
@ -37,8 +39,10 @@
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/ranap/ranap_ies_defs.h>
#include <osmocom/ranap/ranap_msg_factory.h>
#include <osmocom/ranap/iu_helpers.h>
#include <osmocom/hnbgw/context_map.h>
/***********************************************************************
@ -98,25 +102,212 @@ static int cn_ranap_rx_reset_ack(struct hnbgw_cnlink *cnlink,
return 0;
}
struct cnlink_paging {
struct llist_head entry;
struct osmo_mobile_identity mi;
struct osmo_mobile_identity mi2;
time_t timestamp;
};
static int cnlink_paging_destructor(struct cnlink_paging *p)
{
llist_del(&p->entry);
return 0;
}
/* Return current timestamp in *timestamp, and the oldest still valid timestamp according to T3113 timeout. */
static const char *cnlink_paging_gettime(time_t *timestamp_p, time_t *timeout_p)
{
struct timespec now;
time_t timestamp;
/* get timestamp */
if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
return "cannot get timestamp";
timestamp = now.tv_sec;
if (timestamp_p)
*timestamp_p = timestamp;
if (timeout_p)
*timeout_p = timestamp - osmo_tdef_get(hnbgw_T_defs, 3113, OSMO_TDEF_S, 15);
return NULL;
}
static const char *cnlink_paging_add(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi,
const struct osmo_mobile_identity *mi2)
{
struct cnlink_paging *p, *p2;
time_t timestamp;
time_t timeout;
const char *errmsg;
errmsg = cnlink_paging_gettime(&timestamp, &timeout);
if (errmsg)
return errmsg;
/* Prune all paging records that are older than the configured timeout. */
llist_for_each_entry_safe(p, p2, &cnlink->paging, entry) {
if (p->timestamp >= timeout)
continue;
talloc_free(p);
}
/* Add new entry */
p = talloc_zero(cnlink, struct cnlink_paging);
*p = (struct cnlink_paging){
.timestamp = timestamp,
.mi = *mi,
.mi2 = *mi2,
};
llist_add_tail(&p->entry, &cnlink->paging);
talloc_set_destructor(p, cnlink_paging_destructor);
LOG_CNLINK(cnlink, DCN, LOGL_INFO, "Rx Paging from CN for %s %s\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, mi),
osmo_mobile_identity_to_str_c(OTC_SELECT, mi2));
return NULL;
}
static const char *omi_from_ranap_ue_id(struct osmo_mobile_identity *mi, const RANAP_PermanentNAS_UE_ID_t *ranap_mi)
{
if (!ranap_mi)
return "null UE ID";
if (ranap_mi->present != RANAP_PermanentNAS_UE_ID_PR_iMSI)
return talloc_asprintf(OTC_SELECT, "unsupported UE ID type %u in RANAP Paging", ranap_mi->present);
if (ranap_mi->choice.iMSI.size > sizeof(mi->imsi))
return talloc_asprintf(OTC_SELECT, "invalid IMSI size %d > %zu",
ranap_mi->choice.iMSI.size, sizeof(mi->imsi));
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_IMSI,
};
ranap_bcd_decode(mi->imsi, sizeof(mi->imsi), ranap_mi->choice.iMSI.buf, ranap_mi->choice.iMSI.size);
LOGP(DCN, LOGL_DEBUG, "ranap MI %s = %s\n", osmo_hexdump(ranap_mi->choice.iMSI.buf, ranap_mi->choice.iMSI.size),
mi->imsi);
return NULL;
}
static const char *omi_from_ranap_temp_ue_id(struct osmo_mobile_identity *mi, const RANAP_TemporaryUE_ID_t *ranap_tmsi)
{
const OCTET_STRING_t *tmsi_str;
if (!ranap_tmsi)
return "null UE ID";
switch (ranap_tmsi->present) {
case RANAP_TemporaryUE_ID_PR_tMSI:
tmsi_str = &ranap_tmsi->choice.tMSI;
break;
case RANAP_TemporaryUE_ID_PR_p_TMSI:
tmsi_str = &ranap_tmsi->choice.p_TMSI;
break;
default:
return talloc_asprintf(OTC_SELECT, "unsupported Temporary UE ID type %u in RANAP Paging", ranap_tmsi->present);
}
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_TMSI,
.tmsi = asn1str_to_u32(tmsi_str),
};
LOGP(DCN, LOGL_DEBUG, "ranap temp UE ID = %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, mi));
return NULL;
}
static const char *cnlink_paging_add_ranap(struct hnbgw_cnlink *cnlink, RANAP_InitiatingMessage_t *imsg)
{
RANAP_PagingIEs_t ies;
struct osmo_mobile_identity mi = {};
struct osmo_mobile_identity mi2 = {};
RANAP_CN_DomainIndicator_t domain;
const char *errmsg;
if (ranap_decode_pagingies(&ies, &imsg->value) < 0)
return "decoding RANAP IEs failed";
domain = ies.cN_DomainIndicator;
errmsg = omi_from_ranap_ue_id(&mi, &ies.permanentNAS_UE_ID);
if (!errmsg && (ies.presenceMask & PAGINGIES_RANAP_TEMPORARYUE_ID_PRESENT))
errmsg = omi_from_ranap_temp_ue_id(&mi2, &ies.temporaryUE_ID);
ranap_free_pagingies(&ies);
LOG_CNLINK(cnlink, DCN, LOGL_DEBUG, "Decoded Paging: %s %s %s%s%s\n",
ranap_domain_name(domain), osmo_mobile_identity_to_str_c(OTC_SELECT, &mi),
mi2.type ? osmo_mobile_identity_to_str_c(OTC_SELECT, &mi2) : "-",
errmsg ? " -- MI error: " : "",
errmsg ? : "");
if (cnlink->pool->domain != domain)
return talloc_asprintf(OTC_SELECT, "message indicates domain %s, but this is %s on domain %s\n",
ranap_domain_name(domain), cnlink->name, ranap_domain_name(cnlink->pool->domain));
if (errmsg)
return errmsg;
return cnlink_paging_add(cnlink, &mi, &mi2);
}
/* If this cnlink has a recent Paging for the given MI, return true and drop the Paging record.
* Else return false. */
static bool cnlink_match_paging_mi(struct hnbgw_cnlink *cnlink, const struct osmo_mobile_identity *mi, time_t timeout)
{
struct cnlink_paging *p, *p2;
llist_for_each_entry_safe(p, p2, &cnlink->paging, entry) {
if (p->timestamp < timeout) {
talloc_free(p);
continue;
}
if (osmo_mobile_identity_cmp(&p->mi, mi)
&& osmo_mobile_identity_cmp(&p->mi2, mi))
continue;
talloc_free(p);
return true;
}
return false;
}
static struct hnbgw_cnlink *cnlink_find_by_paging_mi(struct hnbgw_cnpool *cnpool, const struct osmo_mobile_identity *mi)
{
struct hnbgw_cnlink *cnlink;
time_t timeout = 0;
const char *errmsg;
errmsg = cnlink_paging_gettime(NULL, &timeout);
if (errmsg)
LOGP(DCN, LOGL_ERROR, "%s\n", errmsg);
llist_for_each_entry(cnlink, &cnpool->cnlinks, entry) {
if (!cnlink_match_paging_mi(cnlink, mi, timeout))
continue;
return cnlink;
}
return NULL;
}
static int cn_ranap_rx_paging_cmd(struct hnbgw_cnlink *cnlink,
RANAP_InitiatingMessage_t *imsg,
const uint8_t *data, unsigned int len)
{
const char *errmsg;
struct hnb_context *hnb;
RANAP_PagingIEs_t ies;
int rc;
rc = ranap_decode_pagingies(&ies, &imsg->value);
if (rc < 0)
return rc;
errmsg = cnlink_paging_add_ranap(cnlink, imsg);
if (errmsg) {
LOG_CNLINK(cnlink, DCN, LOGL_ERROR, "Rx Paging from CN: %s. Dropping paging record."
" Later on, the Paging Response may be forwarded to the wrong CN peer.\n",
errmsg);
return -1;
}
/* FIXME: determine which HNBs to send this Paging command,
* rather than broadcasting to all HNBs */
llist_for_each_entry(hnb, &g_hnbgw->hnb_list, list) {
rc = rua_tx_udt(hnb, data, len);
rua_tx_udt(hnb, data, len);
}
ranap_free_pagingies(&ies);
return 0;
}
@ -314,6 +505,122 @@ static int handle_cn_disc_ind(struct hnbgw_sccp_user *hsu,
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 */
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx)
{
@ -355,9 +662,9 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *ctx)
rc = handle_cn_disc_ind(hsu, &prim->u.disconnect, oph);
break;
case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION):
LOGP(DCN, LOGL_DEBUG, "Ignoring prim %s from SCCP USER SAP\n",
osmo_scu_prim_hdr_name_c(OTC_SELECT, oph));
handle_pcstate_ind(hsu, &prim->u.pcstate);
break;
default:
LOGP(DCN, LOGL_ERROR,
"Received unknown prim %u from SCCP USER SAP\n",
@ -678,6 +985,18 @@ struct hnbgw_cnlink *hnbgw_cnlink_select(struct hnbgw_context_map *map)
bool is_null_nri = false;
uint8_t nri_bitlen = cnpool->use.nri_bitlen;
/* Match IMSI with previous Paging */
if (map->l3.gsm48_msg_type == GSM48_MT_RR_PAG_RESP) {
cnlink = cnlink_find_by_paging_mi(cnpool, &map->l3.mi);
if (cnlink) {
LOG_MAP(map, DCN, LOGL_INFO, "CN link paging record selects %s %d\n", cnpool->peer_name,
cnlink->nr);
rate_ctr_inc(rate_ctr_group_get_ctr(cnlink->ctrs, CNLINK_CTR_CNPOOL_SUBSCR_PAGED));
return cnlink;
}
/* If there is no match, go on with other ways */
}
#define LOG_NRI(LOGLEVEL, FORMAT, ARGS...) \
LOG_MAP(map, DCN, LOGLEVEL, "%s NRI(%dbit)=0x%x=%d: " FORMAT, osmo_mobile_identity_to_str_c(OTC_SELECT, &map->l3.mi), \
nri_bitlen, nri_v, nri_v, ##ARGS)

View File

@ -32,6 +32,7 @@ struct osmo_tdef mgw_fsm_T_defs[] = {
};
struct osmo_tdef hnbgw_T_defs[] = {
{.T = 3113, .default_val = 15, .desc = "Time to keep Paging record, for CN pools with more than one link" },
{.T = 4, .default_val = 5, .desc = "Timeout to receive RANAP RESET ACKNOWLEDGE from an MSC/SGSN" },
{.T = -31, .default_val = 5, .desc = "Timeout for discarding a partially released context map (RUA <-> SCCP)" },
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" },