diff --git a/include/osmocom/hnbgw/context_map.h b/include/osmocom/hnbgw/context_map.h index 6910fe8..d6fc2e7 100644 --- a/include/osmocom/hnbgw/context_map.h +++ b/include/osmocom/hnbgw/context_map.h @@ -2,6 +2,9 @@ #include #include +#include + +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); diff --git a/include/osmocom/hnbgw/hnbgw.h b/include/osmocom/hnbgw/hnbgw.h index 9a46301..76c5b84 100644 --- a/include/osmocom/hnbgw/hnbgw.h +++ b/include/osmocom/hnbgw/hnbgw.h @@ -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); diff --git a/include/osmocom/hnbgw/hnbgw_rua.h b/include/osmocom/hnbgw/hnbgw_rua.h index 4b65115..fa33d6b 100644 --- a/include/osmocom/hnbgw/hnbgw_rua.h +++ b/include/osmocom/hnbgw/hnbgw_rua.h @@ -2,6 +2,7 @@ #include #include +#include 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); diff --git a/src/osmo-hnbgw/context_map.c b/src/osmo-hnbgw/context_map.c index 18f71ce..5a7b48b 100644 --- a/src/osmo-hnbgw/context_map.c +++ b/src/osmo-hnbgw/context_map.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -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 diff --git a/src/osmo-hnbgw/hnbgw.c b/src/osmo-hnbgw/hnbgw.c index 96c7ad1..04ca34c 100644 --- a/src/osmo-hnbgw/hnbgw.c +++ b/src/osmo-hnbgw/hnbgw.c @@ -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) { diff --git a/src/osmo-hnbgw/hnbgw_cn.c b/src/osmo-hnbgw/hnbgw_cn.c index 25baeca..5499e5d 100644 --- a/src/osmo-hnbgw/hnbgw_cn.c +++ b/src/osmo-hnbgw/hnbgw_cn.c @@ -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; } diff --git a/src/osmo-hnbgw/hnbgw_rua.c b/src/osmo-hnbgw/hnbgw_rua.c index e4f345e..56d2724 100644 --- a/src/osmo-hnbgw/hnbgw_rua.c +++ b/src/osmo-hnbgw/hnbgw_rua.c @@ -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; diff --git a/src/osmo-hnbgw/hnbgw_vty.c b/src/osmo-hnbgw/hnbgw_vty.c index d064b7d..c263ab3 100644 --- a/src/osmo-hnbgw/hnbgw_vty.c +++ b/src/osmo-hnbgw/hnbgw_vty.c @@ -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);