libosmo-sccp/src/xua_default_lm_fsm.c

438 lines
13 KiB
C

/* Default XUA Layer Manager */
/* (C) 2017-2021 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* The idea of this default Layer Manager is as follows:
* - we wait until a SCTP connection is established
* - we issue the ASP-UP request and wait for the ASP being in UP state
* - we wait if we receive a M-NOTIFY indication about any AS in this ASP
* - if that's not received, we use RKM to register a routing context
* for our locally configured ASP and expect a positive registration
* result as well as a NOTIFY indication about AS-ACTIVE afterwards.
*/
#include <errno.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/logging.h>
#include <osmocom/sigtran/osmo_ss7.h>
#include <osmocom/sigtran/sigtran_sap.h>
#include <osmocom/sigtran/protocol/m3ua.h>
#include "xua_internal.h"
#include "xua_asp_fsm.h"
#define S(x) (1 << (x))
enum lm_state {
/* idle state, SCTP not connected */
S_IDLE,
/* we're waiting for the ASP-UP to be confirmed */
S_WAIT_ASP_UP,
/* we are waiting for any NOTIFY about an AS in this ASP */
S_WAIT_NOTIFY,
/* we've sent a RK REG REQ and wait for the result */
S_RKM_REG,
/* all systems up, we're communicating */
S_ACTIVE,
};
enum lm_event {
LM_E_SCTP_EST_IND,
LM_E_ASP_UP_CONF,
LM_E_NOTIFY_IND,
LM_E_AS_INACTIVE_IND,
LM_E_AS_ACTIVE_IND,
LM_E_AS_STATUS_IND,
LM_E_RKM_REG_CONF,
LM_E_SCTP_DISC_IND,
};
static const struct value_string lm_event_names[] = {
{ LM_E_SCTP_EST_IND, "SCTP-ESTABLISH.ind" },
{ LM_E_ASP_UP_CONF, "ASP-UP.conf" },
{ LM_E_NOTIFY_IND, "NOTIFY.ind" },
{ LM_E_AS_INACTIVE_IND, "AS-INACTIVE.ind" },
{ LM_E_AS_ACTIVE_IND, "AS-ACTIVE.ind" },
{ LM_E_AS_STATUS_IND, "AS-STATUS.ind" },
{ LM_E_RKM_REG_CONF, "RKM_REG.conf" },
{ LM_E_SCTP_DISC_IND, "SCTP-RELEASE.ind" },
{ 0, NULL }
};
/***********************************************************************
* Timer Handling
***********************************************************************/
const struct osmo_tdef ss7_asp_lm_timer_defaults[SS7_ASP_LM_TIMERS_LEN] = {
{ .T = SS7_ASP_LM_T_WAIT_ASP_UP, .default_val = 20, .unit = OSMO_TDEF_S,
.desc = "Restart ASP after timeout waiting for ASP UP (SG role) / ASP UP ACK (ASP role) (s)" },
{ .T = SS7_ASP_LM_T_WAIT_NOTIFY, .default_val = 2, .unit = OSMO_TDEF_S,
.desc = "Restart ASP after timeout waiting for NOTIFY (s)" },
{ .T = SS7_ASP_LM_T_WAIT_NOTIY_RKM, .default_val = 20, .unit = OSMO_TDEF_S,
.desc = "Restart ASP after timeout waiting for NOTIFY after RKM registration (s)" },
{ .T = SS7_ASP_LM_T_WAIT_RK_REG_RESP, .default_val = 10, .unit = OSMO_TDEF_S,
.desc = "Restart ASP after timeout waiting for RK_REG_RESP (s)" },
{}
};
/* Appendix C.4 of ITU-T Q.714 */
const struct value_string ss7_asp_lm_timer_names[] = {
{ SS7_ASP_LM_T_WAIT_ASP_UP, "wait_asp_up" },
{ SS7_ASP_LM_T_WAIT_NOTIFY, "wait_notify" },
{ SS7_ASP_LM_T_WAIT_NOTIY_RKM, "wait_notify_rkm" },
{ SS7_ASP_LM_T_WAIT_RK_REG_RESP, "wait_rk_reg_resp" },
{}
};
osmo_static_assert(ARRAY_SIZE(ss7_asp_lm_timer_defaults) == (SS7_ASP_LM_TIMERS_LEN) &&
ARRAY_SIZE(ss7_asp_lm_timer_names) == (SS7_ASP_LM_TIMERS_LEN),
assert_ss7_asp_lm_timer_count);
static const struct osmo_tdef_state_timeout lm_fsm_timeouts[32] = {
[S_IDLE] = { },
[S_WAIT_ASP_UP] = { .T = SS7_ASP_LM_T_WAIT_ASP_UP },
[S_WAIT_NOTIFY] = { .T = SS7_ASP_LM_T_WAIT_NOTIFY }, /* SS7_ASP_LM_T_WAIT_NOTIY_RKM if coming from S_RKM_REG */
[S_RKM_REG] = { .T = SS7_ASP_LM_T_WAIT_RK_REG_RESP },
[S_ACTIVE] = { },
};
struct lm_fsm_priv {
struct osmo_ss7_asp *asp;
};
#define lm_fsm_state_chg(fi, NEXT_STATE) \
osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \
lm_fsm_timeouts, \
((struct lm_fsm_priv *)(fi->priv))->asp->cfg.T_defs_lm, \
-1)
static struct osmo_ss7_as *find_first_as_in_asp(struct osmo_ss7_asp *asp)
{
struct osmo_ss7_as *as;
llist_for_each_entry(as, &asp->inst->as_list, list) {
unsigned int i;
for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
if (as->cfg.asps[i] == asp)
return as;
}
}
return NULL;
}
/* handle an incoming RKM registration response */
static int handle_reg_conf(struct osmo_fsm_inst *fi, uint32_t l_rk_id, uint32_t rctx)
{
struct lm_fsm_priv *lmp = fi->priv;
struct osmo_ss7_asp *asp = lmp->asp;
struct osmo_ss7_as *as;
/* update the application server with the routing context as
* allocated/registered by the SG */
as = osmo_ss7_as_find_by_l_rk_id(asp->inst, l_rk_id);
if (!as) {
LOGPFSM(fi, "RKM Result for unknown l_rk_id %u\n", l_rk_id);
return -EINVAL;
}
as->cfg.routing_key.context = rctx;
return 0;
}
static void restart_asp(struct osmo_fsm_inst *fi)
{
struct lm_fsm_priv *lmp = fi->priv;
struct osmo_ss7_asp *asp = lmp->asp;
int log_level = fi->log_level;
osmo_ss7_asp_restart(asp);
osmo_ss7_asp_use_default_lm(asp, log_level);
}
static void lm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lm_fsm_priv *lmp = fi->priv;
switch (event) {
case LM_E_SCTP_EST_IND:
/* Try to transition to ASP-UP, wait to receive message for a few seconds */
lm_fsm_state_chg(fi, S_WAIT_ASP_UP);
osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_UP_REQ, NULL);
break;
}
}
static void lm_wait_asp_up(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case LM_E_ASP_UP_CONF:
/* ASP is up, wait for some time if any NOTIFY
* indications about AS in this ASP are received */
lm_fsm_state_chg(fi, S_WAIT_NOTIFY);
break;
}
}
static int lm_timer_cb(struct osmo_fsm_inst *fi)
{
struct lm_fsm_priv *lmp = fi->priv;
struct osmo_xlm_prim *prim;
struct osmo_ss7_as *as;
switch (fi->T) {
case SS7_ASP_LM_T_WAIT_ASP_UP:
/* we have been waiting for the ASP to come up, but it
* failed to do so */
LOGPFSML(fi, LOGL_NOTICE, "Peer didn't send any ASP_UP in time! Restarting ASP\n");
restart_asp(fi);
break;
case SS7_ASP_LM_T_WAIT_NOTIFY:
if (lmp->asp->cfg.quirks & OSMO_SS7_ASP_QUIRK_NO_NOTIFY) {
/* some implementations don't send the NOTIFY which they SHOULD
* according to RFC4666 (see OS#5145) */
LOGPFSM(fi, "quirk no_notify active; locally emulate AS-INACTIVE.ind\n");
osmo_fsm_inst_dispatch(fi, LM_E_AS_INACTIVE_IND, NULL);
break;
}
/* No AS has reported via NOTIFY that is was
* (statically) configured at the SG for this ASP, so
* let's dynamically register */
lm_fsm_state_chg(fi, S_RKM_REG);
prim = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_REQUEST);
OSMO_ASSERT(prim);
as = find_first_as_in_asp(lmp->asp);
if (!as) {
LOGPFSML(fi, LOGL_ERROR, "Unable to find AS!\n");
restart_asp(fi);
return 0;
}
/* Fill in settings from first AS (TODO: multiple AS support) */
prim->u.rk_reg.key = as->cfg.routing_key;
prim->u.rk_reg.traf_mode = as->cfg.mode;
osmo_xlm_sap_down(lmp->asp, &prim->oph);
break;
case SS7_ASP_LM_T_WAIT_NOTIY_RKM:
/* No AS has reported via NOTIFY even after dynamic RKM
* configuration */
restart_asp(fi);
break;
case SS7_ASP_LM_T_WAIT_RK_REG_RESP:
/* timeout of registration of routing key */
restart_asp(fi);
break;
}
return 0;
}
static void lm_wait_notify(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lm_fsm_priv *lmp = fi->priv;
struct osmo_xlm_prim *oxp = data;
switch (event) {
case LM_E_NOTIFY_IND:
OSMO_ASSERT(oxp->oph.primitive == OSMO_XLM_PRIM_M_NOTIFY);
OSMO_ASSERT(oxp->oph.operation == PRIM_OP_INDICATION);
if (oxp->u.notify.status_type == M3UA_NOTIFY_T_STATCHG &&
(oxp->u.notify.status_info == M3UA_NOTIFY_I_AS_INACT ||
oxp->u.notify.status_info == M3UA_NOTIFY_I_AS_PEND)) {
lm_fsm_state_chg(fi, S_ACTIVE);
osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL);
}
break;
case LM_E_AS_INACTIVE_IND:
/* we now know that an AS is associated with this ASP at
* the SG, and that this AS is currently inactive */
/* request the ASP to go into active state (which
* hopefully will bring the AS to active, too) */
lm_fsm_state_chg(fi, S_ACTIVE);
osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL);
break;
}
};
static void lm_rkm_reg(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lm_fsm_priv *lmp = fi->priv;
struct osmo_xlm_prim *oxp;
int rc;
switch (event) {
case LM_E_RKM_REG_CONF:
oxp = data;
if (oxp->u.rk_reg.status != M3UA_RKM_REG_SUCCESS) {
LOGPFSML(fi, LOGL_NOTICE, "Received RKM_REG_RSP with negative result\n");
restart_asp(fi);
} else {
unsigned long timeout_sec;
rc = handle_reg_conf(fi, oxp->u.rk_reg.key.l_rk_id, oxp->u.rk_reg.key.context);
if (rc < 0)
restart_asp(fi);
/* RKM registration was successful, we can transition to WAIT_NOTIFY
* state and assume that an NOTIFY/AS-INACTIVE arrives within
* T_WAIT_NOTIFY_RKM seconds */
timeout_sec = osmo_tdef_get(lmp->asp->cfg.T_defs_lm, SS7_ASP_LM_T_WAIT_NOTIY_RKM, OSMO_TDEF_S, -1);
osmo_fsm_inst_state_chg(fi, S_WAIT_NOTIFY, timeout_sec, SS7_ASP_LM_T_WAIT_NOTIY_RKM);
}
break;
}
}
static void lm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lm_fsm_priv *lmp = fi->priv;
struct osmo_xlm_prim *oxp;
switch (event) {
case LM_E_AS_INACTIVE_IND:
/* request the ASP to go into active state */
osmo_fsm_inst_dispatch(lmp->asp->fi, XUA_ASP_E_M_ASP_ACTIVE_REQ, NULL);
break;
case LM_E_NOTIFY_IND:
oxp = data;
OSMO_ASSERT(oxp->oph.primitive == OSMO_XLM_PRIM_M_NOTIFY);
OSMO_ASSERT(oxp->oph.operation == PRIM_OP_INDICATION);
if (oxp->u.notify.status_type == M3UA_NOTIFY_T_STATCHG &&
oxp->u.notify.status_info != M3UA_NOTIFY_I_AS_ACT)
restart_asp(fi);
break;
}
}
static void lm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case LM_E_SCTP_DISC_IND:
restart_asp(fi);
break;
}
}
static const struct osmo_fsm_state lm_states[] = {
[S_IDLE] = {
.in_event_mask = S(LM_E_SCTP_EST_IND),
.out_state_mask = S(S_WAIT_ASP_UP),
.name = "IDLE",
.action = lm_idle,
},
[S_WAIT_ASP_UP] = {
.in_event_mask = S(LM_E_ASP_UP_CONF),
.out_state_mask = S(S_WAIT_NOTIFY),
.name = "WAIT_ASP_UP",
.action = lm_wait_asp_up,
},
[S_WAIT_NOTIFY] = {
.in_event_mask = S(LM_E_AS_INACTIVE_IND) | S(LM_E_NOTIFY_IND),
.out_state_mask = S(S_RKM_REG) | S(S_ACTIVE),
.name = "WAIT_NOTIFY",
.action = lm_wait_notify,
},
[S_RKM_REG] = {
.in_event_mask = S(LM_E_RKM_REG_CONF),
.out_state_mask = S(S_WAIT_NOTIFY),
.name = "RKM_REG",
.action = lm_rkm_reg,
},
[S_ACTIVE] = {
.in_event_mask = S(LM_E_AS_INACTIVE_IND) | S(LM_E_NOTIFY_IND),
.name = "ACTIVE",
.action = lm_active,
},
};
/* Map from incoming XLM SAP primitives towards FSM events */
static const struct osmo_prim_event_map lm_event_map[] = {
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_SCTP_ESTABLISH, PRIM_OP_INDICATION, LM_E_SCTP_EST_IND },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_SCTP_RELEASE, PRIM_OP_INDICATION, LM_E_SCTP_DISC_IND },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_ASP_UP, PRIM_OP_CONFIRM, LM_E_ASP_UP_CONF },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_AS_STATUS, PRIM_OP_INDICATION, LM_E_AS_STATUS_IND },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_NOTIFY, PRIM_OP_INDICATION, LM_E_NOTIFY_IND },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_AS_INACTIVE, PRIM_OP_INDICATION, LM_E_AS_INACTIVE_IND },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_AS_ACTIVE, PRIM_OP_INDICATION, LM_E_AS_ACTIVE_IND },
{ XUA_SAP_LM, OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_CONFIRM, LM_E_RKM_REG_CONF },
{ 0, 0, 0, OSMO_NO_EVENT },
};
struct osmo_fsm xua_default_lm_fsm = {
.name = "xua_default_lm",
.states = lm_states,
.num_states = ARRAY_SIZE(lm_states),
.timer_cb = lm_timer_cb,
.event_names = lm_event_names,
.allstate_event_mask = S(LM_E_SCTP_DISC_IND),
.allstate_action = lm_allstate,
.log_subsys = DLSS7,
};
/* layer manager primitive call-back function, registered osmo_ss7 */
static int default_lm_prim_cb(struct osmo_prim_hdr *oph, void *_asp)
{
struct osmo_ss7_asp *asp = _asp;
struct osmo_fsm_inst *fi = asp->lm_priv;
uint32_t event = osmo_event_for_prim(oph, lm_event_map);
char *prim_name = osmo_xlm_prim_name(oph);
LOGPFSM(fi, "Received primitive %s\n", prim_name);
if (event == OSMO_NO_EVENT) {
LOGPFSML(fi, LOGL_NOTICE, "Ignoring primitive %s\n", prim_name);
return 0;
}
osmo_fsm_inst_dispatch(fi, event, oph);
return 0;
}
static const struct osmo_xua_layer_manager default_layer_manager = {
.prim_cb = default_lm_prim_cb,
};
int osmo_ss7_asp_use_default_lm(struct osmo_ss7_asp *asp, int log_level)
{
struct lm_fsm_priv *lmp;
struct osmo_fsm_inst *fi;
if (asp->lm_priv) {
osmo_fsm_inst_term(asp->lm_priv, OSMO_FSM_TERM_ERROR, NULL);
asp->lm_priv = NULL;
}
fi = osmo_fsm_inst_alloc(&xua_default_lm_fsm, asp, NULL, log_level, asp->cfg.name);
lmp = talloc_zero(fi, struct lm_fsm_priv);
if (!lmp) {
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
return -ENOMEM;
}
lmp->asp = asp;
fi->priv = lmp;
asp->lm = &default_layer_manager;
asp->lm_priv = fi;
return 0;
}