1216 lines
36 KiB
C
1216 lines
36 KiB
C
/* SCCP M3UA / SUA ASP osmo_fsm according to RFC3868 4.3.1 */
|
|
/* (C) Copyright 2017 by Harald Welte <laforge@gnumonks.org>
|
|
*
|
|
* All Rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*
|
|
* Based on my earlier Erlang implementation xua_asp_fsm.erl in
|
|
* osmo-ss7.git
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <osmocom/core/fsm.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/core/timer.h>
|
|
#include <osmocom/core/prim.h>
|
|
#include <osmocom/core/logging.h>
|
|
#include <osmocom/core/select.h>
|
|
#include <osmocom/gsm/ipa.h>
|
|
|
|
#include <osmocom/netif/stream.h>
|
|
#include <osmocom/netif/ipa.h>
|
|
|
|
#include <osmocom/sigtran/osmo_ss7.h>
|
|
#include <osmocom/sigtran/sigtran_sap.h>
|
|
#include <osmocom/sigtran/xua_msg.h>
|
|
#include <osmocom/sigtran/protocol/sua.h>
|
|
|
|
#include "xua_asp_fsm.h"
|
|
#include "xua_as_fsm.h"
|
|
#include "xua_internal.h"
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
/* The general idea is:
|
|
* * translate incoming SUA/M3UA msg_class/msg_type to xua_asp_event
|
|
* * propagate state transitions to XUA_AS_FSM via _onenter functions
|
|
* * notify the Layer Management of any relevant changes
|
|
* *
|
|
*/
|
|
|
|
/* According to RFC3868 Section 8 */
|
|
#define XUA_T_ACK_SEC 2
|
|
|
|
static const struct value_string xua_asp_event_names[] = {
|
|
{ XUA_ASP_E_M_ASP_UP_REQ, "M-ASP_UP.req" },
|
|
{ XUA_ASP_E_M_ASP_ACTIVE_REQ, "M-ASP_ACTIVE.req" },
|
|
{ XUA_ASP_E_M_ASP_DOWN_REQ, "M-ASP_DOWN.req" },
|
|
{ XUA_ASP_E_M_ASP_INACTIVE_REQ, "M-ASP_INACTIVE.req" },
|
|
|
|
{ XUA_ASP_E_SCTP_COMM_DOWN_IND, "SCTP-COMM_DOWN.ind" },
|
|
{ XUA_ASP_E_SCTP_RESTART_IND, "SCTP-RESTART.ind" },
|
|
{ XUA_ASP_E_SCTP_EST_IND, "SCTP-EST.ind" },
|
|
|
|
{ XUA_ASP_E_ASPSM_ASPUP, "ASPSM-ASP_UP" },
|
|
{ XUA_ASP_E_ASPSM_ASPUP_ACK, "ASPSM-ASP_UP_ACK" },
|
|
{ XUA_ASP_E_ASPTM_ASPAC, "ASPTM-ASP_AC" },
|
|
{ XUA_ASP_E_ASPTM_ASPAC_ACK, "ASPTM-ASP_AC_ACK" },
|
|
{ XUA_ASP_E_ASPSM_ASPDN, "ASPSM-ASP_DN" },
|
|
{ XUA_ASP_E_ASPSM_ASPDN_ACK, "ASPSM-ASP_DN_ACK" },
|
|
{ XUA_ASP_E_ASPTM_ASPIA, "ASPTM-ASP_IA" },
|
|
{ XUA_ASP_E_ASPTM_ASPIA_ACK, "ASPTM_ASP_IA_ACK" },
|
|
|
|
{ XUA_ASP_E_ASPSM_BEAT, "ASPSM_BEAT" },
|
|
{ XUA_ASP_E_ASPSM_BEAT_ACK, "ASPSM_BEAT_ACK" },
|
|
|
|
{ IPA_ASP_E_ID_RESP, "IPA_CCM_ID_RESP" },
|
|
{ IPA_ASP_E_ID_GET, "IPA_CCM_ID_GET" },
|
|
{ IPA_ASP_E_ID_ACK, "IPA_CCM_ID_ACK" },
|
|
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* private data structure for each FSM instance */
|
|
struct xua_asp_fsm_priv {
|
|
/* pointer back to ASP to which we belong */
|
|
struct osmo_ss7_asp *asp;
|
|
/* Role (ASP/SG/IPSP) */
|
|
enum osmo_ss7_asp_role role;
|
|
|
|
/* routing context[s]: list of 32bit integers */
|
|
/* ACTIVE: traffic mode type, tid label, drn label ? */
|
|
|
|
struct {
|
|
struct osmo_timer_list timer;
|
|
int out_event;
|
|
} t_ack;
|
|
};
|
|
|
|
struct osmo_xlm_prim *xua_xlm_prim_alloc(enum osmo_xlm_prim_type prim_type,
|
|
enum osmo_prim_operation op)
|
|
{
|
|
struct osmo_xlm_prim *prim;
|
|
struct msgb *msg = msgb_alloc_headroom(2048+128, 128, "xua_asp-xlm msgb");
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
prim = (struct osmo_xlm_prim *) msgb_put(msg, sizeof(*prim));
|
|
osmo_prim_init(&prim->oph, XUA_SAP_LM, prim_type, op, msg);
|
|
|
|
return prim;
|
|
}
|
|
|
|
/* Send a XUA LM Primitive to the XUA Layer Manager (LM) */
|
|
void xua_asp_send_xlm_prim(struct osmo_ss7_asp *asp, struct osmo_xlm_prim *prim)
|
|
{
|
|
const struct osmo_xua_layer_manager *lm = asp->lm;
|
|
|
|
if (lm && lm->prim_cb)
|
|
lm->prim_cb(&prim->oph, asp);
|
|
else {
|
|
LOGPASP(asp, DLSS7, LOGL_DEBUG, "No Layer Manager, dropping %s\n",
|
|
osmo_xlm_prim_name(&prim->oph));
|
|
}
|
|
|
|
msgb_free(prim->oph.msg);
|
|
}
|
|
|
|
/* wrapper around send_xlm_prim for primitives without data */
|
|
void xua_asp_send_xlm_prim_simple(struct osmo_ss7_asp *asp,
|
|
enum osmo_xlm_prim_type prim_type,
|
|
enum osmo_prim_operation op)
|
|
{
|
|
struct osmo_xlm_prim *prim = xua_xlm_prim_alloc(prim_type, op);
|
|
if (!prim)
|
|
return;
|
|
xua_asp_send_xlm_prim(asp, prim);
|
|
}
|
|
|
|
static void send_xlm_prim_simple(struct osmo_fsm_inst *fi,
|
|
enum osmo_xlm_prim_type prim_type,
|
|
enum osmo_prim_operation op)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = xafp->asp;
|
|
xua_asp_send_xlm_prim_simple(asp, prim_type, op);
|
|
}
|
|
|
|
/* determine the osmo_ss7_as_traffic_mode to be used by this ASP; will
|
|
* iterate over all AS configured for this ASP. If they're compatible,
|
|
* a single traffic mode is returned as enum osmo_ss7_as_traffic_mode.
|
|
* If they're incompatible, -EINVAL is returned. If there is none
|
|
* configured, -1 is returned */
|
|
static int determine_traf_mode(struct osmo_ss7_asp *asp)
|
|
{
|
|
struct osmo_ss7_as *as;
|
|
int tmode = -1;
|
|
|
|
llist_for_each_entry(as, &asp->inst->as_list, list) {
|
|
if (!osmo_ss7_as_has_asp(as, asp))
|
|
continue;
|
|
/* we only care about traffic modes explicitly set */
|
|
if (!as->cfg.mode_set_by_vty)
|
|
continue;
|
|
if (tmode == -1) {
|
|
/* this is the first AS; we use this traffic mode */
|
|
tmode = as->cfg.mode;
|
|
} else {
|
|
if (tmode != as->cfg.mode)
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return tmode;
|
|
}
|
|
|
|
/* add M3UA_IEI_ROUTE_CTX to xua_msg containig all routing keys of ASs within ASP */
|
|
static int xua_msg_add_asp_rctx(struct xua_msg *xua, struct osmo_ss7_asp *asp)
|
|
{
|
|
struct osmo_ss7_as *as;
|
|
uint32_t rctx[128];
|
|
unsigned int i = 0;
|
|
|
|
/* iterate over all ASs and build array of routing contexts */
|
|
llist_for_each_entry(as, &asp->inst->as_list, list) {
|
|
if (!osmo_ss7_as_has_asp(as, asp))
|
|
continue;
|
|
rctx[i++] = htonl(as->cfg.routing_key.context);
|
|
if (i >= ARRAY_SIZE(rctx)-1) {
|
|
break;
|
|
}
|
|
}
|
|
/* add xUA IE with routing contests to the message (if any) */
|
|
if (i) {
|
|
/* bail out (and not add the IE) if there's only one routing context (and hence
|
|
* only one AS) within this ASP, and that routing context is zero, meaning no routing
|
|
* context IE shall be used */
|
|
if (i == 1 && rctx[0] == 0)
|
|
return 0;
|
|
|
|
xua_msg_add_data(xua, M3UA_IEI_ROUTE_CTX, i*sizeof(uint32_t), (uint8_t *)rctx);
|
|
}
|
|
|
|
/* return count of routing contexts added */
|
|
return i;
|
|
}
|
|
|
|
/* ask the xUA implementation to transmit a specific message */
|
|
static int peer_send(struct osmo_fsm_inst *fi, int out_event, struct xua_msg *in)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = xafp->asp;
|
|
struct xua_msg *xua = xua_msg_alloc();
|
|
struct msgb *msg;
|
|
int rc;
|
|
|
|
switch (out_event) {
|
|
case XUA_ASP_E_ASPSM_ASPUP:
|
|
/* RFC 3868 Ch. 3.5.1 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_UP);
|
|
/* Optional: ASP ID */
|
|
if (asp->asp_id_present)
|
|
xua_msg_add_u32(xua, SUA_IEI_ASP_ID, asp->asp_id);
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPUP_ACK:
|
|
/* RFC3868 Ch. 3.5.2 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_UP_ACK);
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPDN:
|
|
/* RFC3868 Ch. 3.5.3 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_DOWN);
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPDN_ACK:
|
|
/* RFC3868 Ch. 3.5.4 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_DOWN_ACK);
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPSM_BEAT:
|
|
/* RFC3868 Ch. 3.5.5 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_BEAT);
|
|
/* Optional: Heartbeat Data */
|
|
break;
|
|
case XUA_ASP_E_ASPSM_BEAT_ACK:
|
|
/* RFC3868 Ch. 3.5.6 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPSM, SUA_ASPSM_BEAT_ACK);
|
|
/* Optional: Heartbeat Data */
|
|
xua_msg_copy_part(xua, M3UA_IEI_HEARDBT_DATA, in, M3UA_IEI_HEARDBT_DATA);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPAC:
|
|
/* RFC3868 Ch. 3.6.1 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_ACTIVE);
|
|
/* Optional: Traffic Mode Type */
|
|
rc = determine_traf_mode(asp);
|
|
if (rc >= 0)
|
|
xua_msg_add_u32(xua, M3UA_IEI_TRAF_MODE_TYP, osmo_ss7_tmode_to_xua(rc));
|
|
/* Optional: Routing Context */
|
|
xua_msg_add_asp_rctx(xua, asp);
|
|
/* Optional: TID Label */
|
|
/* Optional: DRN Label */
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPAC_ACK:
|
|
/* RFC3868 Ch. 3.6.2 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_ACTIVE_ACK);
|
|
/* Optional: Traffic Mode Type */
|
|
xua_msg_copy_part(xua, M3UA_IEI_TRAF_MODE_TYP, in, M3UA_IEI_TRAF_MODE_TYP);
|
|
/* Optional: Routing Context */
|
|
xua_msg_copy_part(xua, M3UA_IEI_ROUTE_CTX, in, M3UA_IEI_ROUTE_CTX);
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPIA:
|
|
/* RFC3868 Ch. 3.6.3 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_INACTIVE);
|
|
/* Optional: Routing Context */
|
|
xua_msg_add_asp_rctx(xua, asp);
|
|
/* Optional: Info String */
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPIA_ACK:
|
|
/* RFC3868 Ch. 3.6.4 */
|
|
xua->hdr = XUA_HDR(SUA_MSGC_ASPTM, SUA_ASPTM_INACTIVE_ACK);
|
|
/* Optional: Routing Context */
|
|
/* Optional: Info String */
|
|
break;
|
|
}
|
|
|
|
msg = xua_to_msg(SUA_VERSION, xua);
|
|
xua_msg_free(xua);
|
|
if (!msg)
|
|
return -1;
|
|
|
|
return osmo_ss7_asp_send(asp, msg);
|
|
}
|
|
|
|
static int peer_send_error(struct osmo_fsm_inst *fi, uint32_t err_code)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = xafp->asp;
|
|
struct xua_msg *xua = xua_msg_alloc();
|
|
struct msgb *msg;
|
|
|
|
xua->hdr = XUA_HDR(SUA_MSGC_MGMT, SUA_MGMT_ERR);
|
|
xua->hdr.version = SUA_VERSION;
|
|
xua_msg_add_u32(xua, SUA_IEI_ERR_CODE, err_code);
|
|
|
|
msg = xua_to_msg(SUA_VERSION, xua);
|
|
xua_msg_free(xua);
|
|
if (!msg)
|
|
return -1;
|
|
|
|
return osmo_ss7_asp_send(asp, msg);
|
|
}
|
|
|
|
static void xua_t_ack_cb(void *data)
|
|
{
|
|
struct osmo_fsm_inst *fi = data;
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
|
|
LOGPFSML(fi, LOGL_INFO, "T(ack) callback: re-transmitting event %s\n",
|
|
osmo_fsm_event_name(fi->fsm, xafp->t_ack.out_event));
|
|
|
|
/* Re-transmit message */
|
|
peer_send(fi, xafp->t_ack.out_event, NULL);
|
|
|
|
/* Re-start the timer */
|
|
osmo_timer_schedule(&xafp->t_ack.timer, XUA_T_ACK_SEC, 0);
|
|
}
|
|
|
|
static int peer_send_and_start_t_ack(struct osmo_fsm_inst *fi,
|
|
int out_event)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
int rc;
|
|
|
|
rc = peer_send(fi, out_event, NULL);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
xafp->t_ack.out_event = out_event;
|
|
xafp->t_ack.timer.cb = xua_t_ack_cb,
|
|
xafp->t_ack.timer.data = fi;
|
|
|
|
osmo_timer_schedule(&xafp->t_ack.timer, XUA_T_ACK_SEC, 0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static const uint32_t evt_ack_map[_NUM_XUA_ASP_E] = {
|
|
[XUA_ASP_E_ASPSM_ASPUP] = XUA_ASP_E_ASPSM_ASPUP_ACK,
|
|
[XUA_ASP_E_ASPTM_ASPAC] = XUA_ASP_E_ASPTM_ASPAC_ACK,
|
|
[XUA_ASP_E_ASPSM_ASPDN] = XUA_ASP_E_ASPSM_ASPDN_ACK,
|
|
[XUA_ASP_E_ASPTM_ASPIA] = XUA_ASP_E_ASPTM_ASPIA_ACK,
|
|
[XUA_ASP_E_ASPSM_BEAT] = XUA_ASP_E_ASPSM_BEAT_ACK,
|
|
};
|
|
|
|
/* Helper function to dispatch an ASP->AS event to all AS of which this
|
|
* ASP is a memmber. Ignores routing contexts for now. */
|
|
static void dispatch_to_all_as(struct osmo_fsm_inst *fi, uint32_t event)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = xafp->asp;
|
|
struct osmo_ss7_instance *inst = asp->inst;
|
|
struct osmo_ss7_as *as;
|
|
|
|
llist_for_each_entry(as, &inst->as_list, list) {
|
|
if (!osmo_ss7_as_has_asp(as, asp))
|
|
continue;
|
|
osmo_fsm_inst_dispatch(as->fi, event, asp);
|
|
}
|
|
}
|
|
|
|
/* check if expected message was received + stop t_ack */
|
|
static void check_stop_t_ack(struct osmo_fsm_inst *fi, uint32_t event)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
int exp_ack;
|
|
|
|
if (event >= ARRAY_SIZE(evt_ack_map))
|
|
return;
|
|
|
|
exp_ack = evt_ack_map[xafp->t_ack.out_event];
|
|
if (exp_ack && event == exp_ack) {
|
|
LOGPFSML(fi, LOGL_DEBUG, "T(ack) stopped\n");
|
|
osmo_timer_del(&xafp->t_ack.timer);
|
|
}
|
|
}
|
|
|
|
#define ENSURE_ASP_OR_IPSP(fi, event) \
|
|
do { \
|
|
struct xua_asp_fsm_priv *_xafp = fi->priv; \
|
|
if (_xafp->role != OSMO_SS7_ASP_ROLE_ASP && \
|
|
_xafp->role != OSMO_SS7_ASP_ROLE_IPSP) { \
|
|
LOGPFSML(fi, LOGL_ERROR, "event %s not permitted " \
|
|
"in role %s\n", \
|
|
osmo_fsm_event_name(fi->fsm, event), \
|
|
get_value_string(osmo_ss7_asp_role_names, _xafp->role));\
|
|
return; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define ENSURE_SG_OR_IPSP(fi, event) \
|
|
do { \
|
|
struct xua_asp_fsm_priv *_xafp = fi->priv; \
|
|
if (_xafp->role != OSMO_SS7_ASP_ROLE_SG && \
|
|
_xafp->role != OSMO_SS7_ASP_ROLE_IPSP) { \
|
|
LOGPFSML(fi, LOGL_ERROR, "event %s not permitted " \
|
|
"in role %s\n", \
|
|
osmo_fsm_event_name(fi->fsm, event), \
|
|
get_value_string(osmo_ss7_asp_role_names, _xafp->role));\
|
|
return; \
|
|
} \
|
|
} while(0)
|
|
|
|
|
|
/***************
|
|
** FSM states **
|
|
***************/
|
|
|
|
static void xua_asp_fsm_down_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
dispatch_to_all_as(fi, XUA_ASPAS_ASP_DOWN_IND);
|
|
}
|
|
|
|
static void xua_asp_fsm_down(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = xafp->asp;
|
|
struct xua_msg_part *asp_id_ie;
|
|
|
|
check_stop_t_ack(fi, event);
|
|
|
|
switch (event) {
|
|
case XUA_ASP_E_M_ASP_UP_REQ:
|
|
/* only if role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
/* Send M3UA_MSGT_ASPSM_ASPUP and start t_ack */
|
|
peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPSM_ASPUP);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPUP_ACK:
|
|
/* only if role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0);
|
|
/* inform layer manager */
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_UP, PRIM_OP_CONFIRM);
|
|
/* This hack should be in layer manager, but let's try
|
|
* to be smart in case there is no layer manager */
|
|
if (!asp->lm)
|
|
osmo_fsm_inst_dispatch(fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPUP:
|
|
/* only if role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
asp_id_ie = xua_msg_find_tag(data, SUA_IEI_ASP_ID);
|
|
/* Optional ASP Identifier: Store for NTFY */
|
|
if (asp_id_ie) {
|
|
asp->asp_id = xua_msg_part_get_u32(asp_id_ie);
|
|
asp->asp_id_present = true;
|
|
}
|
|
/* send ACK */
|
|
peer_send(fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_UP,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPDN:
|
|
/* only if role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* The SGP MUST send an ASP Down Ack message in response
|
|
* to a received ASP Down message from the ASP even if
|
|
* the ASP is already marked as ASP-DOWN at the SGP. */
|
|
peer_send(fi, XUA_ASP_E_ASPSM_ASPDN_ACK, NULL);
|
|
break;
|
|
case XUA_ASP_E_SCTP_EST_IND:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void xua_asp_fsm_inactive_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
dispatch_to_all_as(fi, XUA_ASPAS_ASP_INACTIVE_IND);
|
|
}
|
|
|
|
static void xua_asp_fsm_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = xafp->asp;
|
|
struct osmo_ss7_as *as;
|
|
struct xua_msg *xua_in;
|
|
uint32_t traf_mode = 0;
|
|
struct xua_msg_part *part;
|
|
int i;
|
|
|
|
check_stop_t_ack(fi, event);
|
|
switch (event) {
|
|
case XUA_ASP_E_M_ASP_ACTIVE_REQ:
|
|
/* send M3UA_MSGT_ASPTM_ASPAC and start t_ack */
|
|
peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPTM_ASPAC);
|
|
break;
|
|
case XUA_ASP_E_M_ASP_DOWN_REQ:
|
|
/* send M3UA_MSGT_ASPSM_ASPDN and start t_ack */
|
|
peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPSM_ASPDN);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPAC_ACK:
|
|
/* only in role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_ACTIVE, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_ACTIVE,
|
|
PRIM_OP_CONFIRM);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPDN_ACK:
|
|
/* only in role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN,
|
|
PRIM_OP_CONFIRM);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPAC:
|
|
xua_in = data;
|
|
/* only in role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
if (xua_msg_find_tag(xua_in, M3UA_IEI_TRAF_MODE_TYP)) {
|
|
traf_mode = xua_msg_get_u32(xua_in, M3UA_IEI_TRAF_MODE_TYP);
|
|
if (traf_mode != M3UA_TMOD_OVERRIDE &&
|
|
traf_mode != M3UA_TMOD_LOADSHARE &&
|
|
traf_mode != M3UA_TMOD_BCAST) {
|
|
peer_send_error(fi, M3UA_ERR_UNSUPP_TRAF_MOD_TYP);
|
|
return;
|
|
}
|
|
}
|
|
if ((part = xua_msg_find_tag(xua_in, M3UA_IEI_ROUTE_CTX))) {
|
|
for (i = 0; i < part->len / sizeof(uint32_t); i++) {
|
|
uint32_t rctx = osmo_load32be(&part->dat[i * sizeof(uint32_t)]);
|
|
as = osmo_ss7_as_find_by_rctx(asp->inst, rctx);
|
|
if (!as) {
|
|
peer_send_error(fi, M3UA_ERR_INVAL_ROUT_CTX);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (traf_mode) { /* if the peer has specified a traffic mode at all */
|
|
enum osmo_ss7_as_traffic_mode tmode = osmo_ss7_tmode_from_xua(traf_mode);
|
|
llist_for_each_entry(as, &asp->inst->as_list, list) {
|
|
if (!osmo_ss7_as_has_asp(as, asp))
|
|
continue;
|
|
|
|
if (!as->cfg.mode_set_by_peer && !as->cfg.mode_set_by_vty) {
|
|
as->cfg.mode = tmode;
|
|
LOGPAS(as, DLSS7, LOGL_INFO,
|
|
"ASPAC: Traffic mode set dynamically by peer to %s\n",
|
|
osmo_ss7_as_traffic_mode_name(as->cfg.mode));
|
|
} else if (!osmo_ss7_as_tmode_compatible_xua(as, traf_mode)) {
|
|
peer_send_error(fi, M3UA_ERR_UNSUPP_TRAF_MOD_TYP);
|
|
return;
|
|
}
|
|
as->cfg.mode_set_by_peer = true;
|
|
}
|
|
}
|
|
|
|
/* send ACK */
|
|
peer_send(fi, XUA_ASP_E_ASPTM_ASPAC_ACK, xua_in);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_ACTIVE, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_ACTIVE,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPDN:
|
|
/* only in role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* send ACK */
|
|
peer_send(fi, XUA_ASP_E_ASPSM_ASPDN_ACK, NULL);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPUP:
|
|
/* only if role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* If an ASP Up message is received and internally the
|
|
* remote ASP is already in the ASP-INACTIVE state, an
|
|
* ASP Up Ack message is returned and no further action
|
|
* is taken. */
|
|
peer_send(fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPIA:
|
|
/* only in role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
peer_send(fi, XUA_ASP_E_ASPTM_ASPIA_ACK, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void xua_asp_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
dispatch_to_all_as(fi, XUA_ASPAS_ASP_ACTIVE_IND);
|
|
}
|
|
|
|
static void xua_asp_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct xua_msg *xua_in;
|
|
check_stop_t_ack(fi, event);
|
|
switch (event) {
|
|
case XUA_ASP_E_ASPSM_ASPDN_ACK:
|
|
/* only in role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0);
|
|
/* inform layer manager */
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN,
|
|
PRIM_OP_CONFIRM);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPIA_ACK:
|
|
/* only in role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0);
|
|
/* inform layer manager */
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_INACTIVE,
|
|
PRIM_OP_CONFIRM);
|
|
break;
|
|
case XUA_ASP_E_M_ASP_DOWN_REQ:
|
|
/* only in role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
/* send M3UA_MSGT_ASPSM_ASPDN and star t_ack */
|
|
peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPSM_ASPDN);
|
|
break;
|
|
case XUA_ASP_E_M_ASP_INACTIVE_REQ:
|
|
/* only in role ASP */
|
|
ENSURE_ASP_OR_IPSP(fi, event);
|
|
/* send M3UA_MSGT_ASPTM_ASPIA and star t_ack */
|
|
peer_send_and_start_t_ack(fi, XUA_ASP_E_ASPTM_ASPIA);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPIA:
|
|
/* only in role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* send ACK */
|
|
peer_send(fi, XUA_ASP_E_ASPTM_ASPIA_ACK, NULL);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_INACTIVE,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPDN:
|
|
/* only in role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* send ACK */
|
|
peer_send(fi, XUA_ASP_E_ASPSM_ASPDN_ACK, NULL);
|
|
/* transition state and inform layer manager */
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_ASPUP:
|
|
/* only if role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* an ASP Up Ack message is returned, as well as
|
|
* an Error message ("Unexpected Message), and the
|
|
* remote ASP state is changed to ASP-INACTIVE in all
|
|
* relevant Application Servers */
|
|
peer_send_error(fi, M3UA_ERR_UNEXPECTED_MSG);
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_INACTIVE, 0, 0);
|
|
peer_send(fi, XUA_ASP_E_ASPSM_ASPUP_ACK, NULL);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_INACTIVE,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPTM_ASPAC:
|
|
xua_in = data;
|
|
/* only in role SG */
|
|
ENSURE_SG_OR_IPSP(fi, event);
|
|
/* send ACK */
|
|
peer_send(fi, XUA_ASP_E_ASPTM_ASPAC_ACK, xua_in);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void xua_asp_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
struct xua_msg *xua;
|
|
|
|
switch (event) {
|
|
case XUA_ASP_E_SCTP_COMM_DOWN_IND:
|
|
case XUA_ASP_E_SCTP_RESTART_IND:
|
|
osmo_timer_del(&xafp->t_ack.timer);
|
|
osmo_fsm_inst_state_chg(fi, XUA_ASP_S_DOWN, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_BEAT:
|
|
xua = data;
|
|
peer_send(fi, XUA_ASP_E_ASPSM_BEAT_ACK, xua);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_BEAT_ACK:
|
|
/* FIXME: stop timer, if any */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int xua_asp_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
/* We don't use the fsm timer, so any calls to this are an error */
|
|
OSMO_ASSERT(0);
|
|
return 0;
|
|
}
|
|
|
|
static void xua_asp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
|
{
|
|
struct xua_asp_fsm_priv *xafp = fi->priv;
|
|
|
|
osmo_timer_del(&xafp->t_ack.timer);
|
|
}
|
|
|
|
static const struct osmo_fsm_state xua_asp_states[] = {
|
|
[XUA_ASP_S_DOWN] = {
|
|
.in_event_mask = S(XUA_ASP_E_M_ASP_UP_REQ) |
|
|
S(XUA_ASP_E_ASPSM_ASPUP) |
|
|
S(XUA_ASP_E_ASPSM_ASPUP_ACK) |
|
|
S(XUA_ASP_E_ASPSM_ASPDN) |
|
|
S(XUA_ASP_E_SCTP_EST_IND),
|
|
.out_state_mask = S(XUA_ASP_S_INACTIVE) |
|
|
S(XUA_ASP_S_DOWN),
|
|
.name = "ASP_DOWN",
|
|
.action = xua_asp_fsm_down,
|
|
.onenter = xua_asp_fsm_down_onenter,
|
|
},
|
|
[XUA_ASP_S_INACTIVE] = {
|
|
.in_event_mask = S(XUA_ASP_E_M_ASP_ACTIVE_REQ) |
|
|
S(XUA_ASP_E_M_ASP_DOWN_REQ) |
|
|
S(XUA_ASP_E_ASPTM_ASPAC) |
|
|
S(XUA_ASP_E_ASPTM_ASPAC_ACK) |
|
|
S(XUA_ASP_E_ASPTM_ASPIA) |
|
|
S(XUA_ASP_E_ASPSM_ASPDN) |
|
|
S(XUA_ASP_E_ASPSM_ASPDN_ACK) |
|
|
S(XUA_ASP_E_ASPSM_ASPUP),
|
|
.out_state_mask = S(XUA_ASP_S_DOWN) |
|
|
S(XUA_ASP_S_ACTIVE),
|
|
.name = "ASP_INACTIVE",
|
|
.action = xua_asp_fsm_inactive,
|
|
.onenter = xua_asp_fsm_inactive_onenter,
|
|
},
|
|
[XUA_ASP_S_ACTIVE] = {
|
|
.in_event_mask = S(XUA_ASP_E_ASPSM_ASPDN) |
|
|
S(XUA_ASP_E_ASPSM_ASPDN_ACK) |
|
|
S(XUA_ASP_E_ASPSM_ASPUP) |
|
|
S(XUA_ASP_E_ASPTM_ASPIA) |
|
|
S(XUA_ASP_E_ASPTM_ASPIA_ACK) |
|
|
S(XUA_ASP_E_ASPTM_ASPAC) |
|
|
S(XUA_ASP_E_M_ASP_DOWN_REQ) |
|
|
S(XUA_ASP_E_M_ASP_INACTIVE_REQ),
|
|
.out_state_mask = S(XUA_ASP_S_INACTIVE) |
|
|
S(XUA_ASP_S_DOWN),
|
|
.name = "ASP_ACTIVE",
|
|
.action = xua_asp_fsm_active,
|
|
.onenter = xua_asp_fsm_active_onenter,
|
|
},
|
|
};
|
|
|
|
|
|
struct osmo_fsm xua_asp_fsm = {
|
|
.name = "XUA_ASP",
|
|
.states = xua_asp_states,
|
|
.num_states = ARRAY_SIZE(xua_asp_states),
|
|
.timer_cb = xua_asp_fsm_timer_cb,
|
|
.log_subsys = DLSS7,
|
|
.event_names = xua_asp_event_names,
|
|
.allstate_event_mask = S(XUA_ASP_E_SCTP_COMM_DOWN_IND) |
|
|
S(XUA_ASP_E_SCTP_RESTART_IND) |
|
|
S(XUA_ASP_E_ASPSM_BEAT) |
|
|
S(XUA_ASP_E_ASPSM_BEAT_ACK),
|
|
.allstate_action = xua_asp_allstate,
|
|
.cleanup = xua_asp_fsm_cleanup,
|
|
};
|
|
|
|
static struct osmo_fsm_inst *ipa_asp_fsm_start(struct osmo_ss7_asp *asp,
|
|
enum osmo_ss7_asp_role role, int log_level);
|
|
|
|
/*! \brief Start a new ASP finite stae machine for given ASP
|
|
* \param[in] asp Application Server Process for which to start FSM
|
|
* \param[in] role Role (ASP, SG, IPSP) of this FSM
|
|
* \param[in] log_level Logging Level for ASP FSM logging
|
|
* \returns FSM instance on success; NULL on error */
|
|
struct osmo_fsm_inst *xua_asp_fsm_start(struct osmo_ss7_asp *asp,
|
|
enum osmo_ss7_asp_role role, int log_level)
|
|
{
|
|
struct osmo_fsm_inst *fi;
|
|
struct xua_asp_fsm_priv *xafp;
|
|
|
|
if (asp->cfg.proto == OSMO_SS7_ASP_PROT_IPA)
|
|
return ipa_asp_fsm_start(asp, role, log_level);
|
|
|
|
/* allocate as child of AS? */
|
|
fi = osmo_fsm_inst_alloc(&xua_asp_fsm, asp, NULL, log_level, asp->cfg.name);
|
|
|
|
xafp = talloc_zero(fi, struct xua_asp_fsm_priv);
|
|
if (!xafp) {
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
|
return NULL;
|
|
}
|
|
xafp->role = role;
|
|
xafp->asp = asp;
|
|
|
|
fi->priv = xafp;
|
|
|
|
return fi;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
* IPA Compatibility FSM
|
|
***********************************************************************/
|
|
|
|
/* The idea here is to have a FSM that handles an IPA / SCCPlite link in
|
|
* a way that the higher-layer code considers it the same like an M3UA
|
|
* or SUA link. We have a couple of different states and some
|
|
* additional events. */
|
|
|
|
enum ipa_asp_state {
|
|
IPA_ASP_S_DOWN = XUA_ASP_S_DOWN,
|
|
IPA_ASP_S_INACTIVE = XUA_ASP_S_INACTIVE,
|
|
IPA_ASP_S_ACTIVE = XUA_ASP_S_ACTIVE,
|
|
IPA_ASP_S_WAIT_ID_RESP, /* Waiting for ID_RESP from peer */
|
|
IPA_ASP_S_WAIT_ID_GET, /* Waiting for ID_GET from peer */
|
|
IPA_ASP_S_WAIT_ID_ACK, /* Waiting for ID_ACK from peer */
|
|
IPA_ASP_S_WAIT_ID_ACK2, /* Waiting for ID_ACK (of ACK) from peer */
|
|
};
|
|
|
|
/* private data structure for each FSM instance */
|
|
struct ipa_asp_fsm_priv {
|
|
/* pointer back to ASP to which we belong */
|
|
struct osmo_ss7_asp *asp;
|
|
/* Role (ASP/SG/IPSP) */
|
|
enum osmo_ss7_asp_role role;
|
|
|
|
/* Structure holding parsed data of the IPA CCM ID exchange */
|
|
struct ipaccess_unit *ipa_unit;
|
|
/* Timer for tracking if no PONG is received in response to PING */
|
|
struct osmo_timer_list pong_timer;
|
|
/* Did we receive IPA ID ACK before IPA ID RESP ? */
|
|
bool ipa_id_ack_rcvd;
|
|
};
|
|
|
|
enum ipa_asp_fsm_t {
|
|
T_WAIT_ID_RESP = 1,
|
|
T_WAIT_ID_ACK,
|
|
T_WAIT_ID_GET,
|
|
};
|
|
|
|
/* get the file descriptor related to a given ASP */
|
|
static int get_fd_from_iafp(struct ipa_asp_fsm_priv *iafp)
|
|
{
|
|
struct osmo_ss7_asp *asp = iafp->asp;
|
|
struct osmo_fd *ofd;
|
|
|
|
if (asp->server)
|
|
ofd = osmo_stream_srv_get_ofd(asp->server);
|
|
else if (asp->client)
|
|
ofd = osmo_stream_cli_get_ofd(asp->client);
|
|
else
|
|
return -1;
|
|
|
|
return ofd->fd;
|
|
}
|
|
|
|
/***************
|
|
** FSM states **
|
|
***************/
|
|
|
|
/* Server + Client: Initial State, wait for M-ASP-UP.req */
|
|
static void ipa_asp_fsm_down(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
int fd = get_fd_from_iafp(iafp);
|
|
|
|
switch (event) {
|
|
case XUA_ASP_E_M_ASP_UP_REQ:
|
|
case XUA_ASP_E_SCTP_EST_IND:
|
|
if (iafp->role == OSMO_SS7_ASP_ROLE_SG) {
|
|
/* Server: Transmit IPA ID GET + Wait for Response */
|
|
if (fd >= 0) {
|
|
ipa_ccm_send_id_req(fd);
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_RESP, 10, T_WAIT_ID_RESP);
|
|
}
|
|
} else {
|
|
/* Client: We simply wait for an ID GET */
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_GET, 10, T_WAIT_ID_GET);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Server: We're waiting for an ID RESP */
|
|
static void ipa_asp_fsm_wait_id_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = iafp->asp;
|
|
int fd = get_fd_from_iafp(iafp);
|
|
struct osmo_ss7_as *as;
|
|
struct tlv_parsed tp;
|
|
struct msgb *msg;
|
|
int rc;
|
|
|
|
switch (event) {
|
|
case IPA_ASP_E_ID_RESP:
|
|
/* resolve the AS based on the identity provided by peer. */
|
|
msg = data;
|
|
rc = ipa_ccm_id_resp_parse(&tp, msgb_l2(msg)+1, msgb_l2len(msg)-1);
|
|
if (rc < 0) {
|
|
LOGPFSML(fi, LOGL_ERROR, "Error %d parsing ID_RESP TLV: %s\n", rc,
|
|
msgb_hexdump(msg));
|
|
goto out_err;
|
|
}
|
|
rc = ipa_ccm_tlv_to_unitdata(iafp->ipa_unit, &tp);
|
|
if (rc < 0) {
|
|
LOGPFSML(fi, LOGL_ERROR, "Error %d parsing ID_RESP: %s\n", rc, msgb_hexdump(msg));
|
|
goto out_err;
|
|
}
|
|
if (!iafp->ipa_unit->unit_name) {
|
|
LOGPFSML(fi, LOGL_NOTICE, "No Unit Name specified by client\n");
|
|
goto out_err;
|
|
}
|
|
as = osmo_ss7_as_find_by_name(asp->inst, iafp->ipa_unit->unit_name);
|
|
if (!as) {
|
|
LOGPFSML(fi, LOGL_NOTICE, "Cannot find any definition for IPA Unit Name '%s'\n",
|
|
iafp->ipa_unit->unit_name);
|
|
goto out_err;
|
|
}
|
|
osmo_ss7_as_add_asp(as, asp->cfg.name);
|
|
/* TODO: OAP Authentication? */
|
|
/* Send ID_ACK */
|
|
if (fd >= 0) {
|
|
ipaccess_send_id_ack(fd);
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_ACK2, 10, T_WAIT_ID_ACK);
|
|
/* If we received the ACK beforehand, submit it now */
|
|
if (iafp->ipa_id_ack_rcvd) {
|
|
iafp->ipa_id_ack_rcvd = false;
|
|
osmo_fsm_inst_dispatch(fi, IPA_ASP_E_ID_ACK, NULL);
|
|
}
|
|
}
|
|
break;
|
|
case IPA_ASP_E_ID_ACK:
|
|
/* Since there's no official spec for IPA and some
|
|
implementations seem to like sending the IPA ID ACK before
|
|
the IPA ID RESP, let's catch it and feed it after we receive
|
|
the IPA ID RESP and we are in correct state */
|
|
iafp->ipa_id_ack_rcvd = true;
|
|
break;
|
|
}
|
|
return;
|
|
out_err:
|
|
osmo_ss7_asp_disconnect(asp);
|
|
return;
|
|
}
|
|
|
|
/* Server: We're waiting for an ID ACK */
|
|
static void ipa_asp_fsm_wait_id_ack2(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
switch (event) {
|
|
case IPA_ASP_E_ID_ACK:
|
|
/* ACK received, we can go to active state now. The
|
|
* ACTIVE onenter function will inform the AS */
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_ACTIVE, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Client: We're waiting for an ID GET */
|
|
static void ipa_asp_fsm_wait_id_get(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
struct osmo_ss7_asp *asp = iafp->asp;
|
|
struct msgb *msg_get, *msg_resp;
|
|
const uint8_t *req_data;
|
|
int data_len;
|
|
int fd;
|
|
|
|
switch (event) {
|
|
case IPA_ASP_E_ID_GET:
|
|
msg_get = data;
|
|
req_data = msgb_l2(msg_get)+1;
|
|
data_len = msgb_l2len(msg_get)-1;
|
|
LOGPFSM(fi, "Received IPA CCM IDENTITY REQUEST for IEs %s\n",
|
|
osmo_hexdump(req_data, data_len));
|
|
/* avoid possible unsigned integer underflow, as ipa_ccm_make_id_resp_from_req()
|
|
* expects an unsigned integer, and in case of a zero-length L2 message we might
|
|
* have data_len == -1 here */
|
|
if (data_len < 0)
|
|
data_len = 0;
|
|
/* Send ID_RESP to server */
|
|
msg_resp = ipa_ccm_make_id_resp_from_req(iafp->ipa_unit, req_data, data_len);
|
|
if (!msg_resp) {
|
|
LOGPFSML(fi, LOGL_ERROR, "Error building IPA CCM IDENTITY RESPONSE\n");
|
|
break;
|
|
}
|
|
osmo_ss7_asp_send(asp, msg_resp);
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_WAIT_ID_ACK, 10, T_WAIT_ID_ACK);
|
|
break;
|
|
case IPA_ASP_E_ID_ACK:
|
|
/* Some SCCPLite MSCs are known to send an ACK directly instead
|
|
* of GET. Support them and skip the GET+RESP handshake by
|
|
* sending ACK2 to server directly */
|
|
fd = get_fd_from_iafp(iafp);
|
|
if (fd >= 0) {
|
|
ipaccess_send_id_ack(fd);
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_ACTIVE, 0, 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Client: We're waiting for an ID ACK */
|
|
static void ipa_asp_fsm_wait_id_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
int fd;
|
|
|
|
switch (event) {
|
|
case IPA_ASP_E_ID_ACK:
|
|
/* Send ACK2 to server */
|
|
fd = get_fd_from_iafp(iafp);
|
|
if (fd >= 0) {
|
|
ipaccess_send_id_ack(fd);
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_ACTIVE, 0, 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ipa_asp_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
dispatch_to_all_as(fi, XUA_ASPAS_ASP_INACTIVE_IND);
|
|
dispatch_to_all_as(fi, XUA_ASPAS_ASP_ACTIVE_IND);
|
|
}
|
|
|
|
/* Server + Client: We're actively transmitting user data */
|
|
static void ipa_asp_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
switch (event) {
|
|
case XUA_ASP_E_M_ASP_DOWN_REQ:
|
|
case XUA_ASP_E_M_ASP_INACTIVE_REQ:
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_DOWN, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ipa_asp_fsm_inactive_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
dispatch_to_all_as(fi, XUA_ASPAS_ASP_INACTIVE_IND);
|
|
}
|
|
|
|
static void ipa_asp_fsm_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
switch (event) {
|
|
case XUA_ASP_E_M_ASP_DOWN_REQ:
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_DOWN, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ipa_asp_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
int fd;
|
|
|
|
switch (event) {
|
|
case XUA_ASP_E_SCTP_COMM_DOWN_IND:
|
|
case XUA_ASP_E_SCTP_RESTART_IND:
|
|
osmo_fsm_inst_state_chg(fi, IPA_ASP_S_DOWN, 0, 0);
|
|
send_xlm_prim_simple(fi, OSMO_XLM_PRIM_M_ASP_DOWN,
|
|
PRIM_OP_INDICATION);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_BEAT:
|
|
/* PING -> PONG */
|
|
fd = get_fd_from_iafp(iafp);
|
|
if (fd >= 0)
|
|
ipaccess_send_pong(fd);
|
|
break;
|
|
case XUA_ASP_E_ASPSM_BEAT_ACK:
|
|
/* stop timer, if any */
|
|
osmo_timer_del(&iafp->pong_timer);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ipa_pong_timer_cb(void *_fi)
|
|
{
|
|
struct osmo_fsm_inst *fi = _fi;
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
|
|
LOGPFSML(fi, LOGL_NOTICE, "Peer didn't respond to PING? with PONG!\n");
|
|
/* kill ASP and (wait for) re-connect */
|
|
osmo_ss7_asp_disconnect(iafp->asp);
|
|
}
|
|
|
|
static int ipa_asp_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct ipa_asp_fsm_priv *iafp = fi->priv;
|
|
|
|
LOGPFSML(fi, LOGL_ERROR, "Timeout waiting for peer response\n");
|
|
/* kill ASP and (wait for) re-connect */
|
|
osmo_ss7_asp_disconnect(iafp->asp);
|
|
return -1;
|
|
}
|
|
|
|
static const struct osmo_fsm_state ipa_asp_states[] = {
|
|
[IPA_ASP_S_DOWN] = {
|
|
.in_event_mask = S(XUA_ASP_E_M_ASP_UP_REQ) |
|
|
S(XUA_ASP_E_SCTP_EST_IND),
|
|
.out_state_mask = S(IPA_ASP_S_WAIT_ID_GET) |
|
|
S(IPA_ASP_S_WAIT_ID_RESP),
|
|
.name = "ASP_DOWN",
|
|
.action = ipa_asp_fsm_down,
|
|
.onenter = xua_asp_fsm_down_onenter,
|
|
},
|
|
/* Server Side */
|
|
[IPA_ASP_S_WAIT_ID_RESP] = {
|
|
.in_event_mask = S(IPA_ASP_E_ID_RESP) |
|
|
S(IPA_ASP_E_ID_ACK),
|
|
.out_state_mask = S(IPA_ASP_S_WAIT_ID_ACK2) |
|
|
S(IPA_ASP_S_DOWN),
|
|
.name = "WAIT_ID_RESP",
|
|
.action = ipa_asp_fsm_wait_id_resp,
|
|
},
|
|
/* Server Side */
|
|
[IPA_ASP_S_WAIT_ID_ACK2] = {
|
|
.in_event_mask = S(IPA_ASP_E_ID_ACK),
|
|
.out_state_mask = S(IPA_ASP_S_ACTIVE) |
|
|
S(IPA_ASP_S_DOWN),
|
|
.name = "WAIT_ID_ACK2",
|
|
.action = ipa_asp_fsm_wait_id_ack2,
|
|
},
|
|
/* Client Side */
|
|
[IPA_ASP_S_WAIT_ID_GET] = {
|
|
.in_event_mask = S(IPA_ASP_E_ID_GET) |
|
|
S(IPA_ASP_E_ID_ACK), /* support broken MSCs skipping GET+RESP */
|
|
.out_state_mask = S(IPA_ASP_S_WAIT_ID_ACK) |
|
|
S(IPA_ASP_S_ACTIVE), /* support broken MSCs skipping GET+RESP */
|
|
.name = "WAIT_ID_GET",
|
|
.action = ipa_asp_fsm_wait_id_get,
|
|
},
|
|
/* Client Side */
|
|
[IPA_ASP_S_WAIT_ID_ACK] = {
|
|
.in_event_mask = S(IPA_ASP_E_ID_ACK),
|
|
.out_state_mask = S(IPA_ASP_S_ACTIVE) |
|
|
S(IPA_ASP_S_DOWN),
|
|
.name = "WAIT_ID_ACK",
|
|
.action = ipa_asp_fsm_wait_id_ack,
|
|
},
|
|
[IPA_ASP_S_ACTIVE] = {
|
|
.in_event_mask = S(XUA_ASP_E_M_ASP_DOWN_REQ) |
|
|
S(XUA_ASP_E_M_ASP_INACTIVE_REQ),
|
|
.out_state_mask = S(IPA_ASP_S_DOWN) |
|
|
S(IPA_ASP_S_INACTIVE),
|
|
.name = "ASP_ACTIVE",
|
|
.action = ipa_asp_fsm_active,
|
|
.onenter = ipa_asp_fsm_active_onenter,
|
|
},
|
|
[IPA_ASP_S_INACTIVE] = {
|
|
.in_event_mask = S(XUA_ASP_E_M_ASP_DOWN_REQ),
|
|
.out_state_mask = S(IPA_ASP_S_DOWN) |
|
|
S(IPA_ASP_S_ACTIVE),
|
|
.name = "ASP_INACTIVE",
|
|
.action = ipa_asp_fsm_inactive,
|
|
.onenter = ipa_asp_fsm_inactive_onenter,
|
|
},
|
|
};
|
|
|
|
struct osmo_fsm ipa_asp_fsm = {
|
|
.name = "IPA_ASP",
|
|
.states = ipa_asp_states,
|
|
.num_states = ARRAY_SIZE(ipa_asp_states),
|
|
.timer_cb = ipa_asp_fsm_timer_cb,
|
|
.log_subsys = DLSS7,
|
|
.event_names = xua_asp_event_names,
|
|
.allstate_event_mask = S(XUA_ASP_E_SCTP_COMM_DOWN_IND) |
|
|
S(XUA_ASP_E_SCTP_RESTART_IND) |
|
|
S(XUA_ASP_E_ASPSM_BEAT) |
|
|
S(XUA_ASP_E_ASPSM_BEAT_ACK),
|
|
.allstate_action = ipa_asp_allstate,
|
|
};
|
|
|
|
|
|
/*! \brief Start a new ASP finite stae machine for given ASP
|
|
* \param[in] asp Application Server Process for which to start FSM
|
|
* \param[in] role Role (ASP, SG, IPSP) of this FSM
|
|
* \param[in] log_level Logging Level for ASP FSM logging
|
|
* \returns FSM instance on success; NULL on error */
|
|
static struct osmo_fsm_inst *ipa_asp_fsm_start(struct osmo_ss7_asp *asp,
|
|
enum osmo_ss7_asp_role role, int log_level)
|
|
{
|
|
struct osmo_fsm_inst *fi;
|
|
struct ipa_asp_fsm_priv *iafp;
|
|
|
|
/* allocate as child of AS? */
|
|
fi = osmo_fsm_inst_alloc(&ipa_asp_fsm, asp, NULL, log_level, asp->cfg.name);
|
|
|
|
iafp = talloc_zero(fi, struct ipa_asp_fsm_priv);
|
|
if (!iafp) {
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
|
|
return NULL;
|
|
}
|
|
iafp->role = role;
|
|
iafp->asp = asp;
|
|
iafp->ipa_unit = talloc_zero(iafp, struct ipaccess_unit);
|
|
iafp->ipa_unit->unit_name = talloc_strdup(iafp->ipa_unit, asp->cfg.name);
|
|
iafp->pong_timer.cb = ipa_pong_timer_cb;
|
|
iafp->pong_timer.data = fi;
|
|
|
|
fi->priv = iafp;
|
|
|
|
if (role == OSMO_SS7_ASP_ROLE_ASP)
|
|
osmo_fsm_inst_dispatch(fi, XUA_ASP_E_M_ASP_UP_REQ, NULL);
|
|
|
|
return fi;
|
|
}
|