context map: introduce RUA and SCCP FSMs to fix leaks

Refactor the entire RUA <-> SCCP connection-oriented message forwarding:
- conquer confusion about hnbgw_context_map release behavior, and
- eradicate SCCP connection leaks.

Finer points:

== Context map state ==
So far, we had a single context map state and some flags to keep track
of both the RUA and the SCCP connections. It was easy to miss connection
cleanup steps, especially on the SCCP side.
Instead, the two FSMs clearly define the RUA and SCCP conn states
separately, and each side takes care of its own release needs for all
possible scenarios.
- When both RUA and SCCP are released, the context map is discarded.
- A context map can stay around to wait for proper SCCP release, even if
  the RUA side has lost the HNB connection.
- Completely drop the async "context mapper garbage collection", because
  the FSMs clarify the release and free steps, synchronously.
- We still keep a (simplified) enum for global context map state, but
  this is only used so that VTY reporting remains mostly unchanged.

== Context map cleanup confusion ==
The function context_map_hnb_released() was the general cleanup function
for a context map. Instead, add separate context_map_free().

== Free context maps separately from HNB ==
When a HNB releases, talloc_steal() the context maps out of the HNB
specific hnb_ctx, so that they are not freed along with the HNB state,
possibly leaving SCCP connections afloat.
(It is still nice to normally keep context maps as talloc children of
their respective hnb_ctx, so talloc reports show which belongs to
which.)

So far, context map handling found the global hnb_gw pointer via
map->hnb_ctx->gw. But in fact, a HNB may disappear at any point in time.
Instead, use a separate hnb_gw pointer in map->gw.

== RUA procedure codes vs. SCCP prims ==
So far, the RUA rx side composed SCCP prims to pass on:

 RUA rx ---SCCP-prim--> RANAP handling ---SCCP-prim--> SCCP tx

That is a source of confusion: a RUA procedure code should not translate
1:1 to SCCP prims, especially for RUA id-Disconnect (see release charts
below).
Instead, move SCCP prim composition over to the SCCP side, using FSM
events to forward:

 RUA rx --event--> RUA FSM --event--> SCCP FSM --SCCP-prim--> SCCP tx
         +RANAP             +RANAP              +RANAP

 RUA tx <--RUA---- RUA FSM <--event-- SCCP FSM <--event-- SCCP rx
          +RANAP             +RANAP              +RANAP

Hence choose the correct prim according to the SCCP FSM state.
- in hnbgw_rua.c, use RUA procedure codes, not prim types.
- via the new FSM events' data args, pass msgb containing RANAP PDUs.

== Fix SCCP Release behavior ==
So far, the normal conn release behavior was

 HNB                 HNBGW                   CN
  | --id-Disconnect--> | ---SCCP-Released--> |  Iu-ReleaseComplete
  |                    | <--SCCP-RLC-------- |  (no data)

Instead, the SCCP release is now in accordance with 3GPP TS 48.006 9.2
'Connection release':

 The MSC sends a SCCP released message. This message shall not contain
 any user data field.

i.e.:

 HNB                 HNBGW                    CN
  | --id-Disconnect--> | ---Data-Form-1(!)--> |  Iu-ReleaseComplete
  |                    | <--SCCP-Released---- |  (no data)
  |                    | ---SCCP-RLC--------> |  (no data)

(Side note, the final SCCP Release Confirm step is taken care of
implicitly by libosmo-sigtran's sccp_scoc.c FSM.)

If the CN fails to respond with SCCP-Released, on new X31 timeout,
osmo-hnbgw will send an SCCP Released to the CN as fallback.

== Memory model for message dispatch ==
So far, an osmo_scu_prim aka "oph" was passed between RUA and SCCP
handling code, and the final dispatch freed it. Every error path had to
take care not to leak any oph.
Instead, use a much easier and much more leakage proof memory model,
inspired by fixeria:
- on rx, dispatch RANAP msgb that live in OTC_SELECT.
- no code path needs to msgb_free() -- the msgb is discarded via
  OTC_SELECT when handling is done, error or no error.
- any code path may also choose to store the msgb for async dispatch,
  using talloc_steal(). The user plane mapping via MGW and UPF do that.
- if any code path does msgb_free(), that would be no problem either
  (but none do so now, for simplicity).

== Layer separation ==
Dispatch *all* connection-oriented RUA tx via the RUA FSM and SCCP tx
via the SCCP FSM, do not call rua_tx_dt() or osmo_sccp_user_sap_down()
directly.

== Memory model for decoded ranap_message IEs ==
Use a talloc destructor to make sure that the ranap_message IEs are
always implicitly freed upon talloc_free(), so that no code path can
possibly forget to do so.

== Implicit cleanup by talloc ==
Use talloc scoping to remove a bunch of explicit cleanup code. For
example, make a chached message a talloc child of its handler:
  talloc_steal(mgw_fsm_priv, message);
  mgw_fsm_priv->ranap_rab_ass_req_message = message;
and later implicitly free 'message' by only freeing the handler:
  talloc_free(mgw_fsm_priv)

Related: SYS#6297
Change-Id: I6ff7e36532ff57c6f2d3e7e419dd22ef27dafd19
changes/31/31431/7
Neels Hofmeyr 4 months ago
parent e67c4b324c
commit ed424d5be4

@ -2,6 +2,7 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/hnbgw/hnbgw.h>
#define LOG_MAP(HNB_CTX_MAP, SUBSYS, LEVEL, FMT, ARGS...) \
LOGHNB((HNB_CTX_MAP) ? (HNB_CTX_MAP)->hnb_ctx : NULL, \
@ -10,12 +11,56 @@
(HNB_CTX_MAP) ? ((HNB_CTX_MAP)->is_ps ? "PS" : "CS") : "NULL", \
##ARGS)
/* All these events' data argument may either be NULL, or point to a RANAP msgb.
* - The msgb shall be in the OTC_SELECT talloc pool, so that they will be deallocated automatically. Some events
* processing will store the msgb for later, in which case it will take over ownership of the msgb by means of
* talloc_steal().
* - For events that may send a RANAP message towards CN via SCCP, the msgb shall have reserved headroom to fit a struct
* osmo_scu_prim. These are: MAP_RUA_EV_RX_*.
* - The RANAP message shall be at msgb_l2().
*/
enum map_rua_fsm_event {
/* Receiving a RUA Connect from HNB. */
MAP_RUA_EV_RX_CONNECT,
/* Receiving some data from HNB via RUA, to forward via SCCP to CN. */
MAP_RUA_EV_RX_DIRECT_TRANSFER,
/* Receiving a RUA Disconnect from HNB. */
MAP_RUA_EV_RX_DISCONNECT,
/* SCCP has received some data from CN to forward via RUA to HNB. */
MAP_RUA_EV_TX_DIRECT_TRANSFER,
/* The CN side is disconnected (e.g. received an SCCP Released), that means we are going gracefully disconnect
* RUA, too. */
MAP_RUA_EV_CN_DISC,
/* All of a sudden, there is no RUA link. For example, HNB vanished / restarted, or SCTP SHUTDOWN on the RUA
* link. Skip RUA disconnect. */
MAP_RUA_EV_HNB_LINK_LOST,
};
/* All these events' data argument is identical to enum map_rua_fsm_event, with this specialisation:
* - The events that may send a RANAP message towards CN via SCCP and hence require a headroom for an osmo_scu_prim are:
* MAP_SCCP_EV_TX_DATA_REQUEST, MAP_SCCP_EV_RAN_DISC.
*/
enum map_sccp_fsm_event {
/* Receiving an SCCP CC from CN. */
MAP_SCCP_EV_RX_CONNECTION_CONFIRM,
/* Receiving some data from CN via SCCP, to forward via RUA to HNB. */
MAP_SCCP_EV_RX_DATA_INDICATION,
/* RUA has received some data from HNB to forward via SCCP to CN. */
MAP_SCCP_EV_TX_DATA_REQUEST,
/* The RAN side received a Disconnect, that means we are going to expect SCCP to disconnect too.
* CN should have received an Iu-ReleaseComplete with or before this, give CN a chance to send an SCCP RLSD;
* after a timeout we will send a non-standard RLSD to the CN instead. */
MAP_SCCP_EV_RAN_DISC,
/* Receiving an SCCP RLSD from CN, or libosmo-sigtran tells us about SCCP connection timeout. All done. */
MAP_SCCP_EV_RX_RELEASED,
};
/* For context_map_get_state(), to combine the RUA and SCCP states, for VTY reporting only. */
enum hnbgw_context_map_state {
MAP_S_NULL,
MAP_S_ACTIVE, /* currently active map */
MAP_S_RESERVED1, /* just disconnected, still resrved */
MAP_S_RESERVED2, /* still reserved */
MAP_S_NUM_STATES /* Number of states, keep this at the end */
MAP_S_CONNECTING, /* not active yet; effectively waiting for SCCP CC */
MAP_S_ACTIVE, /* both RUA and SCCP are connected */
MAP_S_DISCONNECTING, /* not active anymore; effectively waiting for SCCP RLSD */
MAP_S_NUM_STATES /* Number of states, keep this at the end */
};
extern const struct value_string hnbgw_context_map_state_names[];
@ -28,27 +73,36 @@ struct hnbgw_cnlink;
struct hnbgw_context_map {
/* entry in the per-CN list of mappings */
struct llist_head cn_list;
/* entry in the per-HNB list of mappings. */
/* entry in the per-HNB list of mappings. If hnb_ctx == NULL, then this llist entry has been llist_del()eted and
* must not be used. */
struct llist_head hnb_list;
/* Pointer to HNB for this map, to transceive RUA. */
/* Backpointer to global hnb_gw. */
struct hnb_gw *gw;
/* Pointer to HNB for this map, to transceive RUA. If the HNB has disconnected without releasing the RUA
* context, this is NULL. */
struct hnb_context *hnb_ctx;
/* RUA context ID used in RUA messages to/from the hnb_gw. */
uint32_t rua_ctx_id;
/* FSM handling the RUA state for rua_ctx_id. */
struct osmo_fsm_inst *rua_fi;
/* Pointer to CN, to transceive SCCP. */
struct hnbgw_cnlink *cn_link;
/* SCCP User SAP connection ID used in SCCP messages to/from the cn_link. */
uint32_t scu_conn_id;
/* Set to true on SCCP Conn Conf, set to false when an OSMO_SCU_PRIM_N_DISCONNECT has been sent for the SCCP
* User SAP conn. Useful to avoid leaking SCCP connections: guarantee that an OSMO_SCU_PRIM_N_DISCONNECT gets
* sent, even when RUA fails to gracefully disconnect. */
bool scu_conn_active;
/* FSM handling the SCCP state for scu_conn_id. */
struct osmo_fsm_inst *sccp_fi;
/* False for CS, true for PS */
bool is_ps;
enum hnbgw_context_map_state state;
/* When an FSM is asked to disconnect but must still wait for a response, it may set this flag, to continue to
* disconnect once the response is in. In particular, when SCCP is asked to disconnect after an SCCP Connection
* Request was already sent and while waiting for a Connection Confirmed, we should still wait for the SCCP CC
* and immediately release it after that, to not leak the connection. */
bool please_disconnect;
/* FSM instance for the MGW, handles the async MGCP communication necessary to intercept CS RAB Assignment and
* redirect the RTP via the MGW. */
@ -71,17 +125,40 @@ struct hnbgw_context_map {
/* All PS RABs and their GTP tunnel mappings. list of struct ps_rab. Each ps_rab FSM handles the PFCP
* communication for one particular RAB ID. */
struct llist_head ps_rabs;
/* Flag to prevent calling context_map_free() from cleanup code paths triggered by context_map_free() itself. */
bool deallocating;
};
enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map);
enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map);
enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map);
struct hnbgw_context_map *
context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
bool is_ps,
struct hnbgw_cnlink *cn_if_new);
void map_rua_fsm_alloc(struct hnbgw_context_map *map);
void map_sccp_fsm_alloc(struct hnbgw_context_map *map);
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id);
void context_map_hnb_released(struct hnbgw_context_map *map);
int context_map_init(struct hnb_gw *gw);
#define map_rua_dispatch(MAP, EVENT, MSGB) \
_map_rua_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line);
#define map_sccp_dispatch(MAP, EVENT, MSGB) \
_map_sccp_dispatch(MAP, EVENT, MSGB, __FILE__, __LINE__)
int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line);
bool map_rua_is_active(struct hnbgw_context_map *map);
bool map_sccp_is_active(struct hnbgw_context_map *map);
void context_map_check_released(struct hnbgw_context_map *map);
unsigned int msg_has_l2_data(const struct msgb *msg);

@ -19,6 +19,8 @@ enum {
DRUA,
DRANAP,
DMGW,
DHNB,
DCN,
};
#define LOGHNB(HNB_CTX, ss, lvl, fmt, args ...) \
@ -201,3 +203,5 @@ static inline bool hnb_gw_is_gtp_mapping_enabled(const struct hnb_gw *gw)
{
return gw->config.pfcp.remote_addr != NULL;
}
struct msgb *hnbgw_ranap_msg_alloc(const char *name);

@ -3,3 +3,5 @@
#include <osmocom/hnbgw/hnbgw.h>
int hnbgw_cnlink_init(struct hnb_gw *gw, const char *stp_host, uint16_t stp_port, const char *local_ip);
const struct osmo_sccp_addr *hnbgw_cn_get_remote_addr(struct hnb_gw *gw, bool is_ps);

@ -2,6 +2,6 @@
#include <osmocom/ranap/ranap_ies_defs.h>
int handle_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int handle_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
int mgw_fsm_handle_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
int mgw_fsm_release(struct hnbgw_context_map *map);

@ -9,6 +9,6 @@ enum ps_rab_ass_fsm_event {
PS_RAB_ASS_EV_RAB_FAIL,
};
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct osmo_prim_hdr *oph, ranap_message *message);
int hnbgw_gtpmap_rx_rab_ass_req(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
int hnbgw_gtpmap_rx_rab_ass_resp(struct hnbgw_context_map *map, struct msgb *ranap_msg, ranap_message *message);
void hnbgw_gtpmap_release(struct hnbgw_context_map *map);

@ -4,4 +4,5 @@
extern struct osmo_tdef mgw_fsm_T_defs[];
extern struct osmo_tdef ps_T_defs[];
extern struct osmo_tdef cmap_T_defs[];
extern struct osmo_tdef_group hnbgw_tdef_group[];

@ -37,6 +37,8 @@ osmo_hnbgw_SOURCES = \
hnbgw_ranap.c \
hnbgw_vty.c \
context_map.c \
context_map_rua.c \
context_map_sccp.c \
hnbgw_cn.c \
ranap_rab_ass.c \
mgw_fsm.c \

@ -34,13 +34,24 @@
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
const struct value_string hnbgw_context_map_state_names[] = {
{MAP_S_NULL , "not-initialized"},
{MAP_S_ACTIVE , "active"},
{MAP_S_RESERVED1, "inactive-reserved"},
{MAP_S_RESERVED2, "inactive-discard"},
{0, NULL}
{ MAP_S_CONNECTING, "connecting" },
{ MAP_S_ACTIVE, "active" },
{ MAP_S_DISCONNECTING, "disconnecting" },
{}
};
/* Combine the RUA and SCCP states, for VTY reporting only. */
enum hnbgw_context_map_state context_map_get_state(struct hnbgw_context_map *map)
{
enum hnbgw_context_map_state rua = map_rua_get_state(map);
enum hnbgw_context_map_state sccp = map_sccp_get_state(map);
if (rua == MAP_S_ACTIVE && sccp == MAP_S_ACTIVE)
return MAP_S_ACTIVE;
if (rua == MAP_S_DISCONNECTING || sccp == MAP_S_DISCONNECTING)
return MAP_S_DISCONNECTING;
return MAP_S_CONNECTING;
}
/* is a given SCCP USER SAP Connection ID in use for a given CN link? */
static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id)
{
@ -92,11 +103,15 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
uint32_t new_scu_conn_id;
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
if (map->state != MAP_S_ACTIVE)
if (map->cn_link != cn_if_new)
continue;
if (map->cn_link != cn_if_new) {
/* Matching on RUA context id -- only match for RUA context that has not been disconnected yet. If an
* inactive context map for a rua_ctx_id is still around, we may have two entries for the same
* rua_ctx_id around at the same time. That should only stay until its SCCP side is done releasing. */
if (!map_rua_is_active(map))
continue;
}
if (map->rua_ctx_id == rua_ctx_id
&& map->is_ps == is_ps) {
return map;
@ -113,7 +128,7 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
/* allocate a new map entry. */
map = talloc_zero(hnb, struct hnbgw_context_map);
map->state = MAP_S_NULL;
map->gw = hnb->gw;
map->cn_link = cn_if_new;
map->hnb_ctx = hnb;
map->rua_ctx_id = rua_ctx_id;
@ -122,14 +137,43 @@ context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
INIT_LLIST_HEAD(&map->ps_rab_ass);
INIT_LLIST_HEAD(&map->ps_rabs);
map_rua_fsm_alloc(map);
map_sccp_fsm_alloc(map);
/* put it into both lists */
llist_add_tail(&map->hnb_list, &hnb->map_list);
llist_add_tail(&map->cn_list, &cn_if_new->map_list);
map->state = MAP_S_ACTIVE;
return map;
}
int _map_rua_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line)
{
OSMO_ASSERT(map);
if (!map->rua_fi) {
LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive RUA events\n");
return -EINVAL;
}
return _osmo_fsm_inst_dispatch(map->rua_fi, event, ranap_msg, file, line);
}
int _map_sccp_dispatch(struct hnbgw_context_map *map, uint32_t event, struct msgb *ranap_msg,
const char *file, int line)
{
OSMO_ASSERT(map);
if (!map->sccp_fi) {
LOG_MAP(map, DRUA, LOGL_ERROR, "not ready to receive SCCP events\n");
return -EINVAL;
}
return _osmo_fsm_inst_dispatch(map->sccp_fi, event, ranap_msg, file, line);
}
unsigned int msg_has_l2_data(const struct msgb *msg)
{
return msg && msgb_l2(msg) ? msgb_l2len(msg) : 0;
}
/* Map from a CN + Connection ID to HNB + Context ID */
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
@ -137,8 +181,12 @@ context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
struct hnbgw_context_map *map;
llist_for_each_entry(map, &cn->map_list, cn_list) {
if (map->state != MAP_S_ACTIVE)
/* Matching on SCCP conn id -- only match for SCCP conn that has not been disconnected yet. If an
* inactive context map for an scu_conn_id is still around, we may have two entries for the same
* scu_conn_id around at the same time. That should only stay until its RUA side is done releasing. */
if (!map_sccp_is_active(map))
continue;
if (map->scu_conn_id == scu_conn_id) {
return map;
}
@ -152,23 +200,37 @@ context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
void context_map_hnb_released(struct hnbgw_context_map *map)
{
LOG_MAP(map, DMAIN, LOGL_INFO, "Deactivating\n");
/* set the state to reserved. We still show up in the list and
* avoid re-allocation of the context-id until we are cleaned up
* by the context_map garbage collector timer */
/* When a HNB disconnects from RUA, the hnb_context will be freed. This hnbgw_context_map was allocated as a
* child of the hnb_context and would also be deallocated along with the hnb_context. However, the SCCP side for
* this hnbgw_context_map may still be waiting for a graceful release (SCCP RLC). Move this hnbgw_context_map to
* the global hnb_gw talloc ctx, so it can stay around for graceful release / for SCCP timeout.
*
* We could also always allocate hnbgw_context_map under hnb_gw, but it is nice to see which hnb_context owns
* which hnbgw_context_map in a talloc report.
*/
talloc_steal(map->gw, map);
if (map->state != MAP_S_RESERVED2)
map->state = MAP_S_RESERVED1;
/* Tell RUA that the HNB is gone. SCCP release will follow via FSM events. */
map_rua_dispatch(map, MAP_RUA_EV_HNB_LINK_LOST, NULL);
}
/* Is SCCP still active and needs to be disconnected ungracefully? */
if (map->scu_conn_active) {
osmo_sccp_tx_disconn(map->hnb_ctx->gw->sccp.cnlink->sccp_user, map->scu_conn_id, NULL, 0);
map->scu_conn_active = false;
void context_map_free(struct hnbgw_context_map *map)
{
/* guard against FSM termination infinitely looping back here */
if (map->deallocating) {
LOG_MAP(map, DMAIN, LOGL_DEBUG, "context_map_free(): already deallocating\n");
return;
}
map->deallocating = true;
if (map->rua_fi)
osmo_fsm_inst_term(map->rua_fi, OSMO_FSM_TERM_REGULAR, NULL);
OSMO_ASSERT(map->rua_fi == NULL);
if (map->sccp_fi)
osmo_fsm_inst_term(map->sccp_fi, OSMO_FSM_TERM_REGULAR, NULL);
OSMO_ASSERT(map->sccp_fi == NULL);
/* a possibly still existing MGW FSM must be terminated when the context
* map is deactivated. (this is a cornercase) */
if (map->mgw_fi) {
mgw_fsm_release(map);
OSMO_ASSERT(map->mgw_fi == NULL);
@ -177,46 +239,21 @@ void context_map_hnb_released(struct hnbgw_context_map *map)
#if ENABLE_PFCP
hnbgw_gtpmap_release(map);
#endif
}
static struct osmo_timer_list context_map_tmr;
if (map->cn_link)
llist_del(&map->cn_list);
if (map->hnb_ctx)
llist_del(&map->hnb_list);
static void context_map_tmr_cb(void *data)
{
struct hnb_gw *gw = data;
struct hnbgw_cnlink *cn = gw->sccp.cnlink;
struct hnbgw_context_map *map, *next_map;
DEBUGP(DMAIN, "Running context mapper garbage collection\n");
llist_for_each_entry_safe(map, next_map, &cn->map_list, cn_list) {
switch (map->state) {
case MAP_S_RESERVED1:
/* first time we see this reserved
* entry: mark it for stage 2 */
map->state = MAP_S_RESERVED2;
break;
case MAP_S_RESERVED2:
/* second time we see this reserved
* entry: remove it */
LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n");
map->state = MAP_S_NULL;
llist_del(&map->cn_list);
llist_del(&map->hnb_list);
talloc_free(map);
break;
default:
break;
}
}
/* re-schedule this timer */
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n");
talloc_free(map);
}
int context_map_init(struct hnb_gw *gw)
void context_map_check_released(struct hnbgw_context_map *map)
{
context_map_tmr.cb = context_map_tmr_cb;
context_map_tmr.data = gw;
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
return 0;
if (map_rua_is_active(map) || map_sccp_is_active(map)) {
/* still active, do not release yet. */
return;
}
context_map_free(map);
}

@ -0,0 +1,361 @@
/* RUA side FSM of hnbgw_context_map */
/* (C) 2023 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* 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 <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/ranap/ranap_common_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/hnbgw_rua.h>
#include <osmocom/hnbgw/mgw_fsm.h>
enum map_rua_fsm_state {
MAP_RUA_ST_INIT,
MAP_RUA_ST_CONNECTED,
MAP_RUA_ST_DISCONNECTED,
};
static const struct value_string map_rua_fsm_event_names[] = {
OSMO_VALUE_STRING(MAP_RUA_EV_RX_CONNECT),
OSMO_VALUE_STRING(MAP_RUA_EV_RX_DIRECT_TRANSFER),
OSMO_VALUE_STRING(MAP_RUA_EV_RX_DISCONNECT),
OSMO_VALUE_STRING(MAP_RUA_EV_TX_DIRECT_TRANSFER),
OSMO_VALUE_STRING(MAP_RUA_EV_CN_DISC),
OSMO_VALUE_STRING(MAP_RUA_EV_HNB_LINK_LOST),
{}
};
static struct osmo_fsm map_rua_fsm;
static const struct osmo_tdef_state_timeout map_rua_fsm_timeouts[32] = {
[MAP_RUA_ST_INIT] = { .T = -31 },
[MAP_RUA_ST_DISCONNECTED] = { .T = -31 },
};
/* Transition to a state, using the T timer defined in map_rua_fsm_timeouts.
* Assumes local variable fi exists. */
#define map_rua_fsm_state_chg(state) \
OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \
map_rua_fsm_timeouts, \
cmap_T_defs, \
5) == 0)
void map_rua_fsm_alloc(struct hnbgw_context_map *map)
{
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_rua_fsm, map, map, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-RUA-%u", hnb_context_name(map->hnb_ctx), map->rua_ctx_id);
OSMO_ASSERT(map->rua_fi == NULL);
map->rua_fi = fi;
/* trigger the timeout */
map_rua_fsm_state_chg(MAP_RUA_ST_INIT);
}
enum hnbgw_context_map_state map_rua_get_state(struct hnbgw_context_map *map)
{
if (!map || !map->rua_fi)
return MAP_S_DISCONNECTING;
switch (map->rua_fi->state) {
case MAP_RUA_ST_INIT:
return MAP_S_CONNECTING;
case MAP_RUA_ST_CONNECTED:
return MAP_S_ACTIVE;
default:
case MAP_RUA_ST_DISCONNECTED:
return MAP_S_DISCONNECTING;
}
}
bool map_rua_is_active(struct hnbgw_context_map *map)
{
if (!map || !map->rua_fi)
return false;
switch (map->rua_fi->state) {
case MAP_RUA_ST_DISCONNECTED:
return false;
default:
return true;
}
}
static int map_rua_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
/* Return 1 to terminate FSM instance, 0 to keep running */
switch (fi->state) {
default:
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
return 0;
case MAP_RUA_ST_DISCONNECTED:
return 1;
}
}
static int destruct_ranap_cn_rx_co_ies(ranap_message *ranap_message_p)
{
ranap_cn_rx_co_free(ranap_message_p);
return 0;
}
/* Dispatch RANAP message to SCCP, if any. */
static int handle_rx_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
int rc;
if (!msg_has_l2_data(ranap_msg))
return 0;
/* See if it is a RAB Assignment Response message from RUA to SCCP, where we need to change the user plane
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
if (!map->is_ps) {
ranap_message *message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_cn_rx_co_decode2(message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc == 0) {
talloc_set_destructor(message, destruct_ranap_cn_rx_co_ies);
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_handle_rab_ass_resp() takes ownership of prim->oph and (ranap) message */
return mgw_fsm_handle_rab_ass_resp(map, ranap_msg, message);
}
}
#if ENABLE_PFCP
} else if (hnb_gw_is_gtp_mapping_enabled(map->hnb_gw)) {
/* map->is_ps == true and PFCP is enabled in osmo-hnbgw.cfg */
ranap_message *message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_cn_rx_co_decode2(message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc == 0) {
talloc_set_destructor(message, destruct_ranap_cn_rx_co_ies);
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* ps_rab_ass_fsm takes ownership of prim->oph and RANAP message */
return hnbgw_gtpmap_rx_rab_ass_resp(map, ranap_msg, message);
}
}
#endif
}
/* It was not a RAB Assignment Response that needed to be intercepted. Forward as-is to SCCP. */
return map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
}
static int forward_ranap_to_rua(struct hnbgw_context_map *map, struct msgb *ranap_msg)
{
int rc;
if (!msg_has_l2_data(ranap_msg))
return 0;
if (!map->hnb_ctx) {
LOGPFSML(map->rua_fi, LOGL_ERROR, "Cannot transmit RUA DirectTransfer: HNB has disconnected\n");
return -ENOTCONN;
}
rc = rua_tx_dt(map->hnb_ctx, map->is_ps, map->rua_ctx_id, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc)
LOGPFSML(map->rua_fi, LOGL_ERROR, "Failed to transmit RUA DirectTransfer to HNB\n");
return rc;
}
static void map_rua_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data;
switch (event) {
case MAP_RUA_EV_RX_CONNECT:
map_rua_fsm_state_chg(MAP_RUA_ST_CONNECTED);
/* The Connect will never be a RAB Assignment response, so no need for handle_rx_rua() (which decodes
* the RANAP message to detect a RAB Assignment response). Just forward to SCCP as is. */
map_sccp_dispatch(map, MAP_SCCP_EV_TX_DATA_REQUEST, ranap_msg);
return;
case MAP_RUA_EV_RX_DISCONNECT:
case MAP_RUA_EV_CN_DISC:
case MAP_RUA_EV_HNB_LINK_LOST:
/* Unlikely that SCCP is active, but let the SCCP FSM decide about that. */
handle_rx_rua(map, ranap_msg);
/* There is a reason to shut down this RUA connection. Super unlikely, we haven't even processed the
* MAP_RUA_EV_RX_CONNECT that created this FSM. Semantically, RUA is not connected, so we can
* directly go to MAP_RUA_ST_DISCONNECTED. */
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
break;
default:
OSMO_ASSERT(false);
}
}
static void map_rua_tx_disconnect(struct osmo_fsm_inst *fi)
{
struct hnbgw_context_map *map = fi->priv;
RUA_Cause_t rua_cause;
if (!map->hnb_ctx || !map->hnb_ctx->conn) {
/* HNB already disconnected, nothing to do. */
LOGPFSML(fi, LOGL_NOTICE, "HNB vanished, this RUA context cannot disconnect gracefully\n");
return;
}
/* Send Disconnect to RUA without RANAP data. */
rua_cause = (RUA_Cause_t){
.present = RUA_Cause_PR_radioNetwork,
.choice.radioNetwork = RUA_CauseRadioNetwork_network_release,
};
if (rua_tx_disc(map->hnb_ctx, map->is_ps, map->rua_ctx_id, &rua_cause, NULL, 0))
LOGPFSML(fi, LOGL_ERROR, "Failed to send Disconnect to RUA\n");
}
static void map_rua_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data;
switch (event) {
case MAP_RUA_EV_RX_DIRECT_TRANSFER:
/* received DirectTransfer from RUA, forward to SCCP */
handle_rx_rua(map, ranap_msg);
return;
case MAP_RUA_EV_TX_DIRECT_TRANSFER:
/* Someone (usually the SCCP side) wants us to send a RANAP payload to HNB via RUA */
forward_ranap_to_rua(map, ranap_msg);
return;
case MAP_RUA_EV_RX_DISCONNECT:
/* received Disconnect from RUA. forward any payload to SCCP, and change state. */
if (!map_sccp_is_active(map)) {
/* If, unlikely, the SCCP is already gone, changing to MAP_RUA_ST_DISCONNECTED frees the
* hnbgw_context_map. Avoid a use-after-free. */
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
return;
}
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
handle_rx_rua(map, ranap_msg);
return;
case MAP_RUA_EV_HNB_LINK_LOST:
/* The HNB is gone. Cannot gracefully cleanup the RUA connection, just be gone. */
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
return;
case MAP_RUA_EV_CN_DISC:
/* There is a disruptive reason to shut down this RUA connection, HNB is still there */
OSMO_ASSERT(data == NULL);
map_rua_tx_disconnect(fi);
map_rua_fsm_state_chg(MAP_RUA_ST_DISCONNECTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_rua_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
/* For sanity, always tell SCCP to disconnect, if it hasn't done so. Dispatching MAP_SCCP_EV_RAN_DISC may send
* SCCP into MAP_RUA_ST_DISCONNECTED, which calls context_map_check_released() and frees the hnbgw_context_map,
* so don't free it a second time. If SCCP stays active, calling context_map_check_released() has no effect. */
if (map_sccp_is_active(map))
map_sccp_dispatch(map, MAP_SCCP_EV_RAN_DISC, NULL);
else
context_map_check_released(map);
}
static void map_rua_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = data;
if (msg_has_l2_data(ranap_msg))
LOGPFSML(fi, LOGL_ERROR, "RUA not connected, cannot dispatch RANAP message\n");
}
void map_rua_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct hnbgw_context_map *map = fi->priv;
map->rua_fi = NULL;
context_map_check_released(map);
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state map_rua_fsm_states[] = {
[MAP_RUA_ST_INIT] = {
.name = "init",
.in_event_mask = 0
| S(MAP_RUA_EV_RX_CONNECT)
| S(MAP_RUA_EV_RX_DISCONNECT)
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_RUA_ST_INIT)
| S(MAP_RUA_ST_CONNECTED)
| S(MAP_RUA_ST_DISCONNECTED)
,
.action = map_rua_init_action,
},
[MAP_RUA_ST_CONNECTED] = {
.name = "connected",
.in_event_mask = 0
| S(MAP_RUA_EV_RX_DIRECT_TRANSFER)
| S(MAP_RUA_EV_TX_DIRECT_TRANSFER)
| S(MAP_RUA_EV_RX_DISCONNECT)
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.out_state_mask = 0
| S(MAP_RUA_ST_DISCONNECTED)
,
.action = map_rua_connected_action,
},
[MAP_RUA_ST_DISCONNECTED] = {
.name = "disconnected",
.in_event_mask = 0
| S(MAP_RUA_EV_CN_DISC)
| S(MAP_RUA_EV_HNB_LINK_LOST)
,
.onenter = map_rua_disconnected_onenter,
.action = map_rua_disconnected_action,
},
};
static struct osmo_fsm map_rua_fsm = {
.name = "map_rua",
.states = map_rua_fsm_states,
.num_states = ARRAY_SIZE(map_rua_fsm_states),
.log_subsys = DHNB,
.event_names = map_rua_fsm_event_names,
.timer_cb = map_rua_fsm_timer_cb,
.cleanup = map_rua_fsm_cleanup,
};
static __attribute__((constructor)) void map_rua_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&map_rua_fsm) == 0);
}

@ -0,0 +1,546 @@
/* SCCP side FSM of hnbgw_context_map */
/* (C) 2023 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Neels Hofmeyr
*
* 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 <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/ranap/ranap_common_ran.h>
#include <osmocom/hnbgw/hnbgw_cn.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/tdefs.h>
#include <osmocom/hnbgw/mgw_fsm.h>
enum map_sccp_fsm_state {
MAP_SCCP_ST_INIT,
MAP_SCCP_ST_WAIT_CC,
MAP_SCCP_ST_CONNECTED,
MAP_SCCP_ST_WAIT_RLSD,
MAP_SCCP_ST_DISCONNECTED,
};
static const struct value_string map_sccp_fsm_event_names[] = {
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_CONNECTION_CONFIRM),
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_DATA_INDICATION),
OSMO_VALUE_STRING(MAP_SCCP_EV_TX_DATA_REQUEST),
OSMO_VALUE_STRING(MAP_SCCP_EV_RAN_DISC),
OSMO_VALUE_STRING(MAP_SCCP_EV_RX_RELEASED),
{}
};
static struct osmo_fsm map_sccp_fsm;
static const struct osmo_tdef_state_timeout map_sccp_fsm_timeouts[32] = {
[MAP_SCCP_ST_INIT] = { .T = -31 },
[MAP_SCCP_ST_WAIT_CC] = { .T = -31 },
[MAP_SCCP_ST_CONNECTED] = { .T = 0 },
[MAP_SCCP_ST_WAIT_RLSD] = { .T = -31 },
[MAP_SCCP_ST_DISCONNECTED] = { .T = -31 },
};
/* Transition to a state, using the T timer defined in map_sccp_fsm_timeouts.
* Assumes local variable fi exists. */
#define map_sccp_fsm_state_chg(state) \
OSMO_ASSERT(osmo_tdef_fsm_inst_state_chg(fi, state, \
map_sccp_fsm_timeouts, \
cmap_T_defs, \
5) == 0)
void map_sccp_fsm_alloc(struct hnbgw_context_map *map)
{
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&map_sccp_fsm, map, map, LOGL_DEBUG, NULL);
OSMO_ASSERT(fi);
osmo_fsm_inst_update_id_f_sanitize(fi, '-', "%s-SCCP-%u", hnb_context_name(map->hnb_ctx), map->scu_conn_id);
OSMO_ASSERT(map->sccp_fi == NULL);
map->sccp_fi = fi;
/* trigger the timeout */
map_sccp_fsm_state_chg(MAP_SCCP_ST_INIT);
}
enum hnbgw_context_map_state map_sccp_get_state(struct hnbgw_context_map *map)
{
if (!map || !map->sccp_fi)
return MAP_S_DISCONNECTING;
switch (map->sccp_fi->state) {
case MAP_SCCP_ST_INIT:
case MAP_SCCP_ST_WAIT_CC:
return MAP_S_CONNECTING;
case MAP_SCCP_ST_CONNECTED:
return MAP_S_ACTIVE;
default:
case MAP_SCCP_ST_WAIT_RLSD:
case MAP_SCCP_ST_DISCONNECTED:
return MAP_S_DISCONNECTING;
}
}
bool map_sccp_is_active(struct hnbgw_context_map *map)
{
if (!map || !map->sccp_fi)
return false;
switch (map->sccp_fi->state) {
case MAP_SCCP_ST_DISCONNECTED:
return false;
default:
return true;
}
}
static int tx_sccp_cr(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
struct osmo_scu_prim *prim;
int rc;
if (!ranap_msg) {
/* prepare a msgb to send an empty N-Connect prim (but this should never happen in practice) */
ranap_msg = hnbgw_ranap_msg_alloc("SCCP-CR-empty");
}
prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_REQUEST, ranap_msg);
prim->u.connect.called_addr = *hnbgw_cn_get_remote_addr(map->gw, map->is_ps);
prim->u.connect.calling_addr = map->gw->sccp.local_addr;
prim->u.connect.sccp_class = 2;
prim->u.connect.conn_id = map->scu_conn_id;
rc = osmo_sccp_user_sap_down_nofree(map->cn_link->sccp_user, &prim->oph);
if (rc)
LOGPFSML(fi, LOGL_ERROR, "Failed to forward SCCP Connectoin Request to CN\n");
return rc;
}
static int tx_sccp_df1(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
struct osmo_scu_prim *prim;
int rc;
if (!msg_has_l2_data(ranap_msg))
return 0;
prim = (struct osmo_scu_prim *)msgb_push(ranap_msg, sizeof(*prim));
osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, ranap_msg);
prim->u.data.conn_id = map->scu_conn_id;
rc = osmo_sccp_user_sap_down_nofree(map->cn_link->sccp_user, &prim->oph);
if (rc)
LOGPFSML(fi, LOGL_ERROR, "Failed to forward SCCP Data Form 1 to CN\n");
return rc;
}
static int tx_sccp_rlsd(struct osmo_fsm_inst *fi)
{
struct hnbgw_context_map *map = fi->priv;
return osmo_sccp_tx_disconn(map->cn_link->sccp_user, map->scu_conn_id, NULL, 0);
}
static int destruct_ranap_ran_rx_co_ies(ranap_message *ranap_message_p)
{
ranap_ran_rx_co_free(ranap_message_p);
return 0;
}
static int handle_rx_sccp(struct osmo_fsm_inst *fi, struct msgb *ranap_msg)
{
struct hnbgw_context_map *map = fi->priv;
int rc;
/* When there was no message received along with the received event, then there is nothing to forward to RUA. */
if (!msg_has_l2_data(ranap_msg))
return 0;
/* See if it is a RAB Assignment Request message from SCCP to RUA, where we need to change the user plane
* information, for RTP mapping via MGW, or GTP mapping via UPF. */
if (!map->is_ps) {
ranap_message *message;
/* Circuit-Switched. Set up mapping of RTP ports via MGW */
message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_ran_rx_co_decode(message, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc == 0) {
talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies);
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* mgw_fsm_alloc_and_handle_rab_ass_req() takes ownership of (ranap) message */
return handle_rab_ass_req(map, ranap_msg, message);
case RANAP_ProcedureCode_id_Iu_Release:
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
mgw_fsm_release(map);
break;
}
}
#if ENABLE_PFCP
} else {
ranap_message *message;
struct hnb_gw *hnb_gw = cnlink->gw;
/* Packet-Switched. Set up mapping of GTP ports via UPF */
message = talloc_zero(OTC_SELECT, ranap_message);
rc = ranap_ran_rx_co_decode(message, message, msgb_l2(ranap_msg), msgb_l2len(ranap_msg));
if (rc == 0) {
talloc_set_destructor(message, destruct_ranap_ran_rx_co_ies);
switch (message->procedureCode) {
case RANAP_ProcedureCode_id_RAB_Assignment:
/* If a UPF is configured, handle the RAB Assignment via ps_rab_ass_fsm, and replace the
* GTP F-TEIDs in the RAB Assignment message before passing it on to RUA. */
if (hnb_gw_is_gtp_mapping_enabled(hnb_gw)) {
LOGP(DMAIN, LOGL_DEBUG,
"RAB Assignment: setting up GTP tunnel mapping via UPF %s\n",
osmo_sockaddr_to_str_c(OTC_SELECT, &hnb_gw->pfcp.cp_peer->remote_addr));
return hnbgw_gtpmap_rx_rab_ass_req(map, ranap_msg, message);
}
/* If no UPF is configured, directly forward the message as-is (no GTP mapping). */
LOGP(DMAIN, LOGL_DEBUG, "RAB Assignment: no UPF configured, forwarding as-is\n");
break;
case RANAP_ProcedureCode_id_Iu_Release:
/* Any IU Release will terminate the MGW FSM, the message itsself is not passed to the
* FSM code. It is just forwarded normally by map_rua_tx_dt() below. */
hnbgw_gtpmap_release(map);
break;
}
}
#endif
}
/* It was not a RAB Assignment Request that needed to be intercepted. Forward as-is to RUA. */
return map_rua_dispatch(map, MAP_RUA_EV_TX_DIRECT_TRANSFER, ranap_msg);
}
static void map_sccp_init_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data;
switch (event) {
case MAP_SCCP_EV_TX_DATA_REQUEST:
/* In the INIT state, the first MAP_SCCP_EV_TX_DATA_REQUEST will be the RANAP message received from the
* RUA Connect message. Send the SCCP CR and transition to WAIT_CC. */
if (tx_sccp_cr(fi, ranap_msg) == 0)
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_CC);
return;
case MAP_SCCP_EV_RAN_DISC:
/* No CR has been sent yet, just go to disconnected state. */
if (msg_has_l2_data(ranap_msg))
LOG_MAP(map, DLSCCP, LOGL_ERROR, "SCCP not connected, cannot dispatch RANAP message\n");
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_RELEASED:
/* SCCP RLSD received from CN. This will never happen since we haven't even asked for a connection, but
* for completeness: */
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_wait_cc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct hnbgw_context_map *map = fi->priv;
struct msgb *ranap_msg = data;
switch (event) {
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
map_sccp_fsm_state_chg(MAP_SCCP_ST_CONNECTED);
/* Usually doesn't but if the SCCP CC contained data, forward it to RUA */
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_TX_DATA_REQUEST:
LOGPFSML(fi, LOGL_ERROR, "Connection not yet confirmed, cannot forward RANAP to CN\n");
return;
case MAP_SCCP_EV_RAN_DISC:
/* RUA connection was terminated. First wait for the CC before releasing the SCCP conn. */
if (msg_has_l2_data(ranap_msg))
LOGPFSML(fi, LOGL_ERROR, "Connection not yet confirmed, cannot forward RANAP to CN\n");
map->please_disconnect = true;
return;
case MAP_SCCP_EV_RX_RELEASED:
/* SCCP RLSD received from CN. This will never happen since we haven't even received a Connection
* Confirmed, but for completeness: */
handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
if (map->please_disconnect) {
/* SCCP has already been asked to disconnect, so disconnect now that the CC has been received. Send RLSD
* to SCCP (without RANAP data) */
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
}
}
static void map_sccp_connected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = data;
switch (event) {
case MAP_SCCP_EV_RX_DATA_INDICATION:
/* forward RANAP from SCCP to RUA */
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_TX_DATA_REQUEST:
/* Someone (usually the RUA side) wants us to send a RANAP payload to CN via SCCP */
tx_sccp_df1(fi, ranap_msg);
return;
case MAP_SCCP_EV_RAN_DISC:
/* RUA has disconnected, and usually has sent an Iu-ReleaseComplete along with its RUA Disconnect. On
* SCCP, the Iu-ReleaseComplete should still be forwarded as N-Data (SCCP Data Form 1), and we will
* expect the CN to send an SCCP RLSD soon. */
map_sccp_fsm_state_chg(MAP_SCCP_ST_WAIT_RLSD);
tx_sccp_df1(fi, ranap_msg);
return;
case MAP_SCCP_EV_RX_RELEASED:
/* The CN sends an N-Disconnect (SCCP Released) out of the usual sequence. Not what we expected, but
* handle it. */
LOGPFSML(fi, LOGL_ERROR, "CN sends SCCP Released sooner than expected\n");
handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
/* Already connected. Unusual, but if there is data just forward it. */
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC again\n");
handle_rx_sccp(fi, ranap_msg);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_wait_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
/* For sanity, always tell RUA to disconnect, if it hasn't done so. */
if (map_rua_is_active(map))
map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL);
}
static void map_sccp_wait_rlsd_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = data;
switch (event) {
case MAP_SCCP_EV_RX_RELEASED:
/* The CN sends the expected SCCP RLSD.
* Usually there is no data, but if there is just forward it.
* Usually RUA is already disconnected, but let the RUA FSM decide about that. */
handle_rx_sccp(fi, ranap_msg);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return;
case MAP_SCCP_EV_RX_DATA_INDICATION:
/* RUA is probably already disconnected, but let the RUA FSM decide about that. */
handle_rx_sccp(fi, ranap_msg);
return;
case MAP_SCCP_EV_TX_DATA_REQUEST:
case MAP_SCCP_EV_RAN_DISC:
/* Normally, RUA would already disconnected, but since SCCP is officially still connected, we can still
* forward messages there. Already waiting for CN to send the SCCP RLSD. If there is a message, forward
* it, and just continue to time out on the SCCP RLSD. */
tx_sccp_df1(fi, ranap_msg);
return;
case MAP_SCCP_EV_RX_CONNECTION_CONFIRM:
/* Already connected. Unusual, but if there is data just forward it. */
LOGPFSML(fi, LOGL_ERROR, "Already connected, but received SCCP CC\n");
handle_rx_sccp(fi, ranap_msg);
return;
default:
OSMO_ASSERT(false);
}
}
static void map_sccp_disconnected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct hnbgw_context_map *map = fi->priv;
/* For sanity, always tell RUA to disconnect, if it hasn't done so. Dispatching MAP_RUA_EV_CN_DISC may send
* RUA into MAP_RUA_ST_DISCONNECTED, which calls context_map_check_released() and frees the hnbgw_context_map,
* so don't free it a second time. If RUA stays active, calling context_map_check_released() has no effect. */
if (map_rua_is_active(map))
map_rua_dispatch(map, MAP_RUA_EV_CN_DISC, NULL);
else
context_map_check_released(map);
}
static void map_sccp_disconnected_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct msgb *ranap_msg = data;
if (msg_has_l2_data(ranap_msg))
LOGPFSML(fi, LOGL_ERROR, "SCCP not connected, cannot dispatch RANAP message\n");
}
static int map_sccp_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct hnbgw_context_map *map = fi->priv;
/* Return 1 to terminate FSM instance, 0 to keep running */
switch (fi->state) {
case MAP_SCCP_ST_INIT:
case MAP_SCCP_ST_WAIT_CC:
/* cannot sent SCCP RLSD, because the other side hasn't responded with the remote reference. */
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return 0;
case MAP_SCCP_ST_CONNECTED:
case MAP_SCCP_ST_WAIT_RLSD:
/* send SCCP RLSD. libosmo-sigtran/sccp_scoc.c will do the SCCP connection cleanup.
* (It will repeatedly send SCCP RLSD until the peer responded with SCCP RLC, or until the
* sccp_connection->t_int timer expires, and the sccp_connection is freed.) */
if (map->cn_link && map->cn_link->sccp_user)
tx_sccp_rlsd(fi);
map_sccp_fsm_state_chg(MAP_SCCP_ST_DISCONNECTED);
return 0;
default:
case MAP_SCCP_ST_DISCONNECTED:
return 1;
}
}
void map_sccp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct hnbgw_context_map *map = fi->priv;
map->sccp_fi = NULL;
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state map_sccp_fsm_states[] = {
[MAP_SCCP_ST_INIT] = {
.name = "init",
.in_event_mask = 0
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RX_RELEASED)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_INIT)
| S(MAP_SCCP_ST_WAIT_CC)
| S(MAP_SCCP_ST_DISCONNECTED)
,
.action = map_sccp_init_action,
},
[MAP_SCCP_ST_WAIT_CC] = {
.name = "wait_cc",
.in_event_mask = 0
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RX_RELEASED)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_CONNECTED)
| S(MAP_SCCP_ST_DISCONNECTED)
,
.action = map_sccp_wait_cc_action,
},
[MAP_SCCP_ST_CONNECTED] = {
.name = "connected",
.in_event_mask = 0
| S(MAP_SCCP_EV_RX_DATA_INDICATION)
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RX_RELEASED)
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_WAIT_RLSD)
| S(MAP_SCCP_ST_DISCONNECTED)
,
.onenter = map_sccp_connected_onenter,
.action = map_sccp_connected_action,
},
[MAP_SCCP_ST_WAIT_RLSD] = {
.name = "wait_rlsd",
.in_event_mask = 0
| S(MAP_SCCP_EV_RX_RELEASED)
| S(MAP_SCCP_EV_RX_DATA_INDICATION)
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
| S(MAP_SCCP_EV_RX_CONNECTION_CONFIRM)
,
.out_state_mask = 0
| S(MAP_SCCP_ST_DISCONNECTED)
,
.onenter = map_sccp_wait_rlsd_onenter,
.action = map_sccp_wait_rlsd_action,
},
[MAP_SCCP_ST_DISCONNECTED] = {
.name = "disconnected",
.in_event_mask = 0
| S(MAP_SCCP_EV_TX_DATA_REQUEST)
| S(MAP_SCCP_EV_RAN_DISC)
,
.onenter = map_sccp_disconnected_onenter,
.action = map_sccp_disconnected_action,
},
};
static struct osmo_fsm map_sccp_fsm = {
.name = "map_sccp",
.states = map_sccp_fsm_states,
.num_states = ARRAY_SIZE(map_sccp_fsm_states),
.log_subsys = DCN,
.event_names = map_sccp_fsm_event_names,
.timer_cb = map_sccp_fsm_timer_cb,
.cleanup = map_sccp_fsm_cleanup,
};
static __attribute__((constructor)) void map_sccp_fsm_register(void)
{
OSMO_ASSERT(osmo_fsm_register(&map_sccp_fsm) == 0);
}

@ -98,8 +98,6 @@ static struct hnb_gw *hnb_gw_create(void *ctx)
INIT_LLIST_HEAD(&gw->hnb_list);
INIT_LLIST_HEAD(&gw->ue_list);
context_map_init(gw);
gw->mgw_pool = mgcp_client_pool_alloc(gw);
gw->config.mgcp_client = talloc_zero(tall_hnb_ctx, struct mgcp_client_conf);
mgcp_client_conf_init(gw->config.mgcp_client);
@ -397,18 +395,16 @@ void hnb_context_release_ue_state(struct hnb_context *ctx)
/* deactivate all context maps */
llist_for_each_entry_safe(map, map2, &ctx->map_list, hnb_list) {
/* remove it from list, as HNB context will soon be
* gone. Let's hope the second osmo_llist_del in the
* map garbage collector works fine? */
llist_del(&map->hnb_list);
llist_del(&map->cn_list);
context_map_hnb_released(map);
/* hnbgw_context_map will remove itself from lists when it is ready. */
}
ue_context_free_by_hnb(ctx->gw, ctx);
}
void hnb_context_release(struct hnb_context *ctx)
{
struct hnbgw_context_map *map;
LOGHNB(ctx, DMAIN, LOGL_INFO, "Releasing HNB context\n");
/* remove from the list of HNB contexts */
@ -424,6 +420,14 @@ void hnb_context_release(struct hnb_context *ctx)
osmo_stream_srv_destroy(ctx->conn);
} /* else: we are called from closed_cb, so conn is being freed separately */
/* hnbgw_context_map are still listed in ctx->map_list, but we are freeing ctx. Remove all entries from the
* list, but keep the hnbgw_context_map around for graceful release. They are also listed under
* hnbgw_cnlink->map_list, and will remove themselves when ready. */
while ((map = llist_first_entry_or_null(&ctx->map_list, struct hnbgw_context_map, hnb_list))) {
llist_del(&map->hnb_list);
map->hnb_ctx = NULL;
}
talloc_free(ctx);
}
@ -469,6 +473,16 @@ static const struct log_info_cat log_cat[] = {
.color = "\033[1;33m",
.description = "Media Gateway",
},
[DHNB] = {
.name = "DHNB", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = OSMO_LOGCOLOR_CYAN,
.description = "HNB side (via RUA)",
},
[DCN] = {
.name = "DCN", .loglevel = LOGL_NOTICE, .enabled = 1,
.color = OSMO_LOGCOLOR_DARKYELLOW,
.description = "Core Network side (via SCCP)",
},
};
static const struct log_info hnbgw_log_info = {
@ -841,3 +855,12 @@ int main(int argc, char **argv)
/* not reached */
exit(0);
}
struct msgb *hnbgw_ranap_msg_alloc(const char *name)
{
struct msgb *ranap_msg;
ranap_msg = msgb_alloc_c(OTC_SELECT, sizeof(struct osmo_scu_prim) + 1500, name);
msgb_reserve(ranap_msg, sizeof(struct osmo_scu_prim));
ranap_msg->l2h = ranap_msg->data;
return ranap_msg;
}