add option to send SCCP CR without payload

It is reported that a third-party SGSN is rejecting SCCP CR when the
SCCP message part exceeds a certain length. The solution is to first
send an SCCP CR without payload, and send the payload in a DT later.

Add config option

  hnbgw
   sccp cr max-payload-len <0-999999>

If the RANAP payload surpasses the given length, osmo-hnbgw will first
send an SCCP CR without payload, cache the RANAP payload, and put that
in an SCCP DT once the SCCP CC is received.

The original idea was to limit the size of the entire SCCP part of the
message, but I'm currently not sure how to determine that without
copying much of the osmo_sccp code. I figured using a limit on the RANAP
payload is sufficient. To avoid the error with above third-party SGSN,
the easy solution is to set max-payload-len to 0, so that we always get
a separate SCCP CR without payload.

Related: SYS#5968
Related: I827e081eaacfb8e76684ed1560603e6c8f896c38 (osmo-ttcn3-hacks)
Change-Id: If0c5c0a76e5230bf22871f527dcb2dbdf34d7328
This commit is contained in:
Neels Hofmeyr 2022-06-07 17:35:56 +02:00
parent afbcae6366
commit 2c91bd66a1
8 changed files with 122 additions and 13 deletions

View File

@ -2,6 +2,9 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/rua/RUA_CN-DomainIndicator.h>
struct msgb;
enum hnbgw_context_map_state {
MAP_S_NULL,
@ -33,6 +36,9 @@ struct hnbgw_context_map {
bool is_ps;
/* SCCP User SAP connection ID */
uint32_t scu_conn_id;
/* Pending data to be sent: when we send an "empty" SCCP CR first, the initial RANAP message will be sent in a
* separate DT once the CR is confirmed. This caches the initial RANAP message. */
struct msgb *cached_msg;
enum hnbgw_context_map_state state;
@ -49,6 +55,8 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id);
int context_map_send_cached_msg(struct hnbgw_context_map *map);
void context_map_deactivate(struct hnbgw_context_map *map);
int context_map_init(struct hnb_gw *gw);

View File

@ -134,6 +134,7 @@ struct hnb_gw {
bool hnbap_allow_tmsi;
/*! print hnb-id (true) or MCC-MNC-LAC-RAC-SAC (false) in logs */
bool log_prefix_hnb_id;
unsigned int max_sccp_cr_payload_len;
struct mgcp_client_conf *mgcp_client;
} config;
/*! SCTP listen socket for incoming connections */
@ -175,3 +176,5 @@ void hnb_context_release(struct hnb_context *ctx);
void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx);
int hnbgw_vty_go_parent(struct vty *vty);
bool hnbgw_requires_empty_sccp_cr(struct hnb_gw *gw, unsigned int ranap_msg_len);

View File

@ -2,6 +2,7 @@
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/rua/RUA_Cause.h>
#include <osmocom/rua/RUA_CN-DomainIndicator.h>
int hnbgw_rua_rx(struct hnb_context *hnb, struct msgb *msg);
int hnbgw_rua_init(void);
@ -11,3 +12,9 @@ int rua_tx_dt(struct hnb_context *hnb, int is_ps, uint32_t context_id,
const uint8_t *data, unsigned int len);
int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
const RUA_Cause_t *cause, const uint8_t *data, unsigned int len);
int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len);

View File

@ -25,6 +25,7 @@
#include <osmocom/core/timer.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
@ -130,6 +131,20 @@ context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
return NULL;
}
int context_map_send_cached_msg(struct hnbgw_context_map *map)
{
int rc;
if (!map || !map->cached_msg)
return 0;
rc = rua_to_scu(map->hnb_ctx,
map->is_ps ? RUA_CN_DomainIndicator_ps_domain : RUA_CN_DomainIndicator_cs_domain,
OSMO_SCU_PRIM_N_DATA, map->rua_ctx_id, 0,
msgb_data(map->cached_msg), msgb_length(map->cached_msg));
msgb_free(map->cached_msg);
map->cached_msg = NULL;
return rc;
}
void context_map_deactivate(struct hnbgw_context_map *map)
{
/* set the state to reserved. We still show up in the list and

View File

@ -88,6 +88,10 @@ static struct hnb_gw *hnb_gw_create(void *ctx)
gw->config.iuh_local_port = IUH_DEFAULT_SCTP_PORT;
gw->config.log_prefix_hnb_id = true;
/* No limit by default, always include the initial RANAP message in the SCCP CR towards the CN.
* 999999 is the maximum value in hnbgw_vty.c */
gw->config.max_sccp_cr_payload_len = 999999;
gw->next_ue_ctx_id = 23;
INIT_LLIST_HEAD(&gw->hnb_list);
INIT_LLIST_HEAD(&gw->ue_list);
@ -343,6 +347,11 @@ void hnb_context_release(struct hnb_context *ctx)
talloc_free(ctx);
}
bool hnbgw_requires_empty_sccp_cr(struct hnb_gw *gw, unsigned int ranap_msg_len)
{
return ranap_msg_len > gw->config.max_sccp_cr_payload_len;
}
/*! call-back when the listen FD has something to read */
static int accept_cb(struct osmo_stream_srv_link *srv, int fd)
{

View File

@ -339,6 +339,10 @@ static int handle_cn_conn_conf(struct hnbgw_cnlink *cnlink,
/* Nothing needs to happen for RUA, RUA towards the HNB doesn't seem to know any confirmations to its CONNECT
* operation. */
/* If our initial SCCP CR was sent without data payload, then the initial RANAP message is cached and waiting to
* be sent as soon as the SCCP connection is confirmed. See if that is the case, send cached data. */
context_map_send_cached_msg(context_map_by_cn(cnlink, param->conn_id));
return 0;
}

View File

@ -177,11 +177,11 @@ int rua_tx_disc(struct hnb_context *hnb, int is_ps, uint32_t context_id,
/* forward a RUA message to the SCCP User API to SCCP */
static int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len)
int rua_to_scu(struct hnb_context *hnb,
RUA_CN_DomainIndicator_t cN_DomainIndicator,
enum osmo_scu_prim_type type,
uint32_t context_id, uint32_t cause,
const uint8_t *data, unsigned int len)
{
struct msgb *msg;
struct osmo_scu_prim *prim;
@ -225,9 +225,9 @@ static int rua_to_scu(struct hnb_context *hnb,
default:
map = context_map_alloc_by_hnb(hnb, context_id, is_ps, cn);
OSMO_ASSERT(map);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u scu_conn_id %u\n",
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_to_scu() %s to %s, rua_ctx_id %u scu_conn_id %u data-len %u\n",
cn_domain_indicator_to_str(cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr),
map->rua_ctx_id, map->scu_conn_id);
map->rua_ctx_id, map->scu_conn_id, len);
}
/* add primitive header */
@ -365,21 +365,69 @@ static int rua_rx_init_connect(struct msgb *msg, ANY_t *in)
struct hnb_context *hnb = msg->dst;
uint32_t context_id;
int rc;
const uint8_t *data;
unsigned int data_len;
rc = rua_decode_connecties(&ies, in);
if (rc < 0)
return rc;
context_id = asn1bitstr_to_u24(&ies.context_ID);
data = ies.ranaP_Message.buf;
data_len = ies.ranaP_Message.size;
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s)\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id,
ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal");
LOGHNB(hnb, DRUA, LOGL_DEBUG, "RUA %s Connect.req(ctx=0x%x, %s, RANAP.size=%u)\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), context_id,
ies.establishment_Cause == RUA_Establishment_Cause_emergency_call ? "emergency" : "normal",
data_len);
if (hnbgw_requires_empty_sccp_cr(hnb->gw, data_len)) {
/* Do not include data in the SCCP CR, to avoid hitting a message size limit at the remote end that may
* lead to rejection. */
bool is_ps;
struct osmo_sccp_addr *remote_addr;
struct hnbgw_context_map *map;
switch (ies.cN_DomainIndicator) {
case RUA_CN_DomainIndicator_cs_domain:
remote_addr = &hnb->gw->sccp.iucs_remote_addr;
is_ps = false;
break;
case RUA_CN_DomainIndicator_ps_domain:
remote_addr = &hnb->gw->sccp.iups_remote_addr;
is_ps = true;
break;
default:
LOGHNB(hnb, DRUA, LOGL_ERROR, "Unsupported Domain %ld\n", ies.cN_DomainIndicator);
rua_free_connecties(&ies);
return -1;
}
if (!hnb->gw->sccp.cnlink) {
LOGHNB(hnb, DRUA, LOGL_NOTICE, "CN=NULL, discarding message\n");
rua_free_connecties(&ies);
return 0;
}
map = context_map_alloc_by_hnb(hnb, context_id, is_ps, hnb->gw->sccp.cnlink);
OSMO_ASSERT(map);
OSMO_ASSERT(map->is_ps == is_ps);
LOGHNB(hnb, DRUA, LOGL_DEBUG, "rua_rx_init_connect() %s to %s, rua_ctx_id %u scu_conn_id %u;"
" Sending SCCP CR without payload, caching %u octets\n",
cn_domain_indicator_to_str(ies.cN_DomainIndicator), osmo_sccp_addr_dump(remote_addr),
map->rua_ctx_id, map->scu_conn_id, data_len);
map->cached_msg = msgb_alloc_c(map, data_len, "map.cached_msg");
OSMO_ASSERT(map->cached_msg);
memcpy(msgb_put(map->cached_msg, data_len), data, data_len);
/* Data is cached for after CR is confirmed, send SCCP CR but omit payload. */
data = NULL;
data_len = 0;
}
rc = rua_to_scu(hnb, ies.cN_DomainIndicator, OSMO_SCU_PRIM_N_CONNECT,
context_id, 0, ies.ranaP_Message.buf,
ies.ranaP_Message.size);
context_id, 0, data, data_len);
rua_free_connecties(&ies);
return rc;

View File

@ -330,6 +330,18 @@ DEFUN(cfg_hnbgw_log_prefix, cfg_hnbgw_log_prefix_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_hnbgw_max_sccp_cr_payload_len, cfg_hnbgw_max_sccp_cr_payload_len_cmd,
"sccp cr max-payload-len <0-999999>",
"Configure SCCP behavior\n"
"Configure SCCP Connection Request\n"
"Set an upper bound for payload data length included directly in the CR. If an initial RUA message has a"
" RANAP payload larger than this value (octets), send an SCCP CR without data, followed by an SCCP DT."
" This may be necessary if the remote component has a size limit on valid SCCP CR messages.\n")
{
g_hnb_gw->config.max_sccp_cr_payload_len = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_hnbgw_iucs_remote_addr,
cfg_hnbgw_iucs_remote_addr_cmd,
"remote-addr NAME",
@ -355,6 +367,8 @@ static int config_write_hnbgw(struct vty *vty)
vty_out(vty, "hnbgw%s", VTY_NEWLINE);
vty_out(vty, " log-prefix %s%s", g_hnb_gw->config.log_prefix_hnb_id ? "hnb-id" : "umts-cell-id",
VTY_NEWLINE);
if (g_hnb_gw->config.max_sccp_cr_payload_len != 999999)
vty_out(vty, " sccp cr max-payload-len %u%s", g_hnb_gw->config.max_sccp_cr_payload_len, VTY_NEWLINE);
return CMD_SUCCESS;
}
@ -421,6 +435,7 @@ void hnbgw_vty_init(struct hnb_gw *gw, void *tall_ctx)
install_element(HNBGW_NODE, &cfg_hnbgw_rnc_id_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_log_prefix_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_max_sccp_cr_payload_len_cmd);
install_element(HNBGW_NODE, &cfg_hnbgw_iuh_cmd);
install_node(&iuh_node, config_write_hnbgw_iuh);