707 lines
20 KiB
C
707 lines
20 KiB
C
/*
|
|
* (C) 2019 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/linuxlist.h>
|
|
#include <osmocom/core/logging.h>
|
|
#include <osmocom/core/fsm.h>
|
|
#include <osmocom/sigtran/sccp_helpers.h>
|
|
|
|
#include <osmocom/msc/ran_peer.h>
|
|
#include <osmocom/msc/sccp_ran.h>
|
|
#include <osmocom/msc/msub.h>
|
|
#include <osmocom/msc/msc_i.h>
|
|
#include <osmocom/msc/msc_a.h>
|
|
#include <osmocom/msc/vlr.h>
|
|
#include <osmocom/msc/ran_conn.h>
|
|
#include <osmocom/msc/cell_id_list.h>
|
|
|
|
static struct osmo_fsm ran_peer_fsm;
|
|
|
|
static __attribute__((constructor)) void ran_peer_init()
|
|
{
|
|
OSMO_ASSERT( osmo_fsm_register(&ran_peer_fsm) == 0);
|
|
}
|
|
|
|
/* Allocate a RAN peer with FSM instance. To deallocate, call osmo_fsm_inst_term(ran_peer->fi). */
|
|
static struct ran_peer *ran_peer_alloc(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr)
|
|
{
|
|
struct ran_peer *rp;
|
|
struct osmo_fsm_inst *fi;
|
|
char *sccp_addr;
|
|
char *pos;
|
|
|
|
fi = osmo_fsm_inst_alloc(&ran_peer_fsm, sri, NULL, LOGL_DEBUG, NULL);
|
|
OSMO_ASSERT(fi);
|
|
|
|
/* Unfortunately, osmo_sccp_inst_addr_name() returns "RI=SSN_PC,PC=0.24.1,SSN=BSSAP" but neither commas nor
|
|
* full-stops are allowed as FSM inst id. Make it "RI=SSN_PC:PC-0-24-1:SSN-BSSAP". */
|
|
sccp_addr = osmo_sccp_inst_addr_name(sri->sccp, peer_addr);
|
|
for (pos = sccp_addr; *pos; pos++) {
|
|
if (*pos == ',')
|
|
*pos = ':';
|
|
else if (*pos == '.' || *pos == '=')
|
|
*pos = '-';
|
|
}
|
|
osmo_fsm_inst_update_id_f(fi, "%s:%s", osmo_rat_type_name(sri->ran->type), sccp_addr);
|
|
|
|
rp = talloc_zero(fi, struct ran_peer);
|
|
OSMO_ASSERT(rp);
|
|
*rp = (struct ran_peer){
|
|
.sri = sri,
|
|
.peer_addr = *peer_addr,
|
|
.fi = fi,
|
|
};
|
|
INIT_LLIST_HEAD(&rp->cells_seen);
|
|
fi->priv = rp;
|
|
|
|
llist_add(&rp->entry, &sri->ran_peers);
|
|
|
|
return rp;
|
|
}
|
|
|
|
struct ran_peer *ran_peer_find_or_create(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr)
|
|
{
|
|
struct ran_peer *rp = ran_peer_find(sri, peer_addr);
|
|
if (rp)
|
|
return rp;
|
|
return ran_peer_alloc(sri, peer_addr);
|
|
}
|
|
|
|
struct ran_peer *ran_peer_find(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr)
|
|
{
|
|
struct ran_peer *rp;
|
|
llist_for_each_entry(rp, &sri->ran_peers, entry) {
|
|
if (osmo_sccp_addr_ri_cmp(peer_addr, &rp->peer_addr))
|
|
continue;
|
|
return rp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ran_peer_cells_seen_add(struct ran_peer *ran_peer, const struct gsm0808_cell_id *cid)
|
|
{
|
|
if (!cell_id_list_add_cell(ran_peer, &ran_peer->cells_seen, cid))
|
|
return;
|
|
LOG_RAN_PEER_CAT(ran_peer, DPAG, LOGL_NOTICE, "Added seen cell to this RAN peer: %s\n",
|
|
gsm0808_cell_id_name(cid));
|
|
}
|
|
|
|
static const struct osmo_tdef_state_timeout ran_peer_fsm_timeouts[32] = {
|
|
[RAN_PEER_ST_WAIT_RX_RESET_ACK] = { .T = -1 },
|
|
[RAN_PEER_ST_DISCARDING] = { .T = -2 },
|
|
};
|
|
|
|
#define ran_peer_state_chg(RAN_PEER, NEXT_STATE) \
|
|
osmo_tdef_fsm_inst_state_chg((RAN_PEER)->fi, NEXT_STATE, ran_peer_fsm_timeouts, g_sccp_tdefs, 5)
|
|
|
|
void ran_peer_discard_all_conns(struct ran_peer *rp)
|
|
{
|
|
struct ran_conn *conn, *next;
|
|
|
|
ran_peer_for_each_ran_conn_safe(conn, next, rp) {
|
|
ran_conn_discard(conn);
|
|
}
|
|
}
|
|
|
|
/* TODO: create an sccp_ran_ops.rx_reset(_ack) to handle this differently on 2g and 3G */
|
|
/* We expect RAN peer to provide use with an Osmocom extension TLV in BSSMAP_RESET to
|
|
* announce Osmux support */
|
|
static void ran_peer_update_osmux_support(struct ran_peer *rp, struct msgb *msg)
|
|
{
|
|
struct tlv_parsed tp;
|
|
int rc;
|
|
bool old_value = rp->remote_supports_osmux;
|
|
|
|
OSMO_ASSERT(msg);
|
|
msg->l3h = msg->l2h + sizeof(struct bssmap_header);
|
|
rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0);
|
|
if (rc < 0)
|
|
LOG_RAN_PEER(rp, LOGL_NOTICE, "Failed parsing TLV looking for Osmux support\n");
|
|
|
|
if (TLVP_PRESENT(&tp, GSM0808_IE_OSMO_OSMUX_SUPPORT)) {
|
|
rp->remote_supports_osmux = true;
|
|
} else {
|
|
rp->remote_supports_osmux = false;
|
|
}
|
|
|
|
if (old_value != rp->remote_supports_osmux)
|
|
LOG_RAN_PEER(rp, LOGL_INFO, "BSC detected AoIP Osmux support changed: %d->%d\n",
|
|
old_value, rp->remote_supports_osmux);
|
|
}
|
|
|
|
/* Drop all SCCP connections for this ran_peer, respond with RESET ACKNOWLEDGE and move to READY state. */
|
|
static void ran_peer_rx_reset(struct ran_peer *rp, struct msgb* msg)
|
|
{
|
|
struct msgb *reset_ack;
|
|
|
|
ran_peer_discard_all_conns(rp);
|
|
|
|
ran_peer_update_osmux_support(rp, msg);
|
|
|
|
reset_ack = rp->sri->ran->sccp_ran_ops.make_reset_msg(rp->sri, SCCP_RAN_MSG_RESET_ACK);
|
|
|
|
if (!reset_ack) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to compose RESET ACKNOWLEDGE message\n");
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
|
|
return;
|
|
}
|
|
|
|
if (sccp_ran_down_l2_cl(rp->sri, &rp->peer_addr, reset_ack)) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to send RESET ACKNOWLEDGE message\n");
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
|
|
msgb_free(reset_ack);
|
|
return;
|
|
}
|
|
|
|
LOG_RAN_PEER(rp, LOGL_INFO, "Sent RESET ACKNOWLEDGE\n");
|
|
|
|
/* sccp_ran_down_l2_cl() doesn't free msgb */
|
|
msgb_free(reset_ack);
|
|
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_READY);
|
|
}
|
|
|
|
static void ran_peer_rx_reset_ack(struct ran_peer *rp, struct msgb* msg)
|
|
{
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_READY);
|
|
ran_peer_update_osmux_support(rp, msg);
|
|
}
|
|
|
|
void ran_peer_reset(struct ran_peer *rp)
|
|
{
|
|
struct msgb *reset;
|
|
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET_ACK);
|
|
ran_peer_discard_all_conns(rp);
|
|
|
|
reset = rp->sri->ran->sccp_ran_ops.make_reset_msg(rp->sri, SCCP_RAN_MSG_RESET);
|
|
|
|
if (!reset) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to compose RESET message\n");
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
|
|
return;
|
|
}
|
|
|
|
if (sccp_ran_down_l2_cl(rp->sri, &rp->peer_addr, reset)) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Failed to send RESET message\n");
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ran_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ran_peer *rp = fi->priv;
|
|
struct ran_peer_ev_ctx *ctx = data;
|
|
struct msgb *msg = ctx->msg;
|
|
|
|
switch (event) {
|
|
case RAN_PEER_EV_MSG_UP_CL:
|
|
switch (rp->sri->ran->sccp_ran_ops.is_reset_msg(rp->sri, msg)) {
|
|
case 1:
|
|
osmo_fsm_inst_dispatch(fi, RAN_PEER_EV_RX_RESET, msg);
|
|
return;
|
|
case 2:
|
|
osmo_fsm_inst_dispatch(fi, RAN_PEER_EV_RX_RESET_ACK, msg);
|
|
return;
|
|
default:
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled ConnectionLess message received: %s\n",
|
|
rp->sri->ran->sccp_ran_ops.msg_name(rp->sri, msg));
|
|
return;
|
|
}
|
|
|
|
default:
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void clear_and_disconnect(struct ran_peer *rp, uint32_t conn_id)
|
|
{
|
|
struct msgb *clear;
|
|
struct ran_msg ran_enc_msg = {
|
|
.msg_type = RAN_MSG_CLEAR_COMMAND,
|
|
.clear_command = {
|
|
.gsm0808_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE,
|
|
},
|
|
};
|
|
|
|
clear = rp->sri->ran->ran_encode(rp->fi, &ran_enc_msg);
|
|
if (!clear
|
|
|| sccp_ran_down_l2_co(rp->sri, conn_id, clear))
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Cannot sent Clear command\n");
|
|
|
|
sccp_ran_disconnect(rp->sri, conn_id, 0);
|
|
}
|
|
|
|
void ran_peer_st_wait_rx_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ran_peer *rp = fi->priv;
|
|
struct ran_peer_ev_ctx *ctx;
|
|
struct msgb *msg;
|
|
|
|
switch (event) {
|
|
|
|
case RAN_PEER_EV_MSG_UP_CO:
|
|
case RAN_PEER_EV_MSG_UP_CO_INITIAL:
|
|
ctx = data;
|
|
OSMO_ASSERT(ctx);
|
|
|
|
if (rp->sri->ignore_missing_reset) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Receiving CO message on RAN peer that has not done a proper RESET yet."
|
|
" Accepting RAN peer implicitly (legacy compat)\n");
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_READY);
|
|
osmo_fsm_inst_dispatch(rp->fi, event, data);
|
|
return;
|
|
}
|
|
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Receiving CO message on RAN peer that has not done a proper RESET yet."
|
|
" Disconnecting on incoming message, sending RESET to RAN peer.\n");
|
|
/* No valid RESET procedure has happened here yet. Usually, we're expecting the RAN peer (BSC,
|
|
* RNC) to first send a RESET message before sending Connection Oriented messages. So if we're
|
|
* getting a CO message, likely we've just restarted or something. Send a RESET to the peer. */
|
|
|
|
/* Make sure the MS / UE properly disconnects. */
|
|
clear_and_disconnect(rp, ctx->conn_id);
|
|
|
|
ran_peer_reset(rp);
|
|
return;
|
|
|
|
case RAN_PEER_EV_RX_RESET:
|
|
msg = (struct msgb*)data;
|
|
ran_peer_rx_reset(rp, msg);
|
|
return;
|
|
|
|
default:
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ran_peer_st_wait_rx_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ran_peer *rp = fi->priv;
|
|
struct ran_peer_ev_ctx *ctx;
|
|
struct msgb *msg;
|
|
|
|
switch (event) {
|
|
|
|
case RAN_PEER_EV_RX_RESET_ACK:
|
|
msg = (struct msgb*)data;
|
|
ran_peer_rx_reset_ack(rp, msg);
|
|
return;
|
|
|
|
case RAN_PEER_EV_MSG_UP_CO:
|
|
case RAN_PEER_EV_MSG_UP_CO_INITIAL:
|
|
ctx = data;
|
|
OSMO_ASSERT(ctx);
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Receiving CO message on RAN peer that has not done a proper RESET yet."
|
|
" Disconnecting on incoming message, sending RESET to RAN peer.\n");
|
|
sccp_ran_disconnect(rp->sri, ctx->conn_id, 0);
|
|
/* No valid RESET procedure has happened here yet. */
|
|
ran_peer_reset(rp);
|
|
return;
|
|
|
|
case RAN_PEER_EV_RX_RESET:
|
|
msg = (struct msgb*)data;
|
|
ran_peer_rx_reset(rp, msg);
|
|
return;
|
|
|
|
default:
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static struct ran_conn *new_incoming_conn(struct ran_peer *rp, uint32_t conn_id)
|
|
{
|
|
struct gsm_network *net = rp->sri->user_data;
|
|
struct msub *msub;
|
|
struct msc_i *msc_i;
|
|
struct msc_a *msc_a;
|
|
struct ran_conn *ran_conn;
|
|
|
|
msub = msub_alloc(net);
|
|
OSMO_ASSERT(msub);
|
|
msc_i = msc_i_alloc(msub, rp->sri->ran);
|
|
OSMO_ASSERT(msc_i);
|
|
|
|
ran_conn = ran_conn_create_incoming(rp, conn_id);
|
|
if (!ran_conn) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Cannot allocate ran_conn\n");
|
|
return NULL;
|
|
}
|
|
msc_i_set_ran_conn(msc_i, ran_conn);
|
|
|
|
msc_a = msc_a_alloc(msub, rp->sri->ran);
|
|
OSMO_ASSERT(msc_a);
|
|
|
|
return msc_i->ran_conn;
|
|
}
|
|
|
|
void ran_peer_st_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct ran_peer *rp = fi->priv;
|
|
struct ran_peer_ev_ctx *ctx;
|
|
struct ran_conn *conn;
|
|
struct an_apdu an_apdu;
|
|
struct msgb *msg;
|
|
|
|
switch (event) {
|
|
|
|
case RAN_PEER_EV_MSG_UP_CO_INITIAL:
|
|
ctx = data;
|
|
OSMO_ASSERT(ctx);
|
|
OSMO_ASSERT(!ctx->conn);
|
|
OSMO_ASSERT(ctx->msg);
|
|
|
|
conn = new_incoming_conn(rp, ctx->conn_id);
|
|
if (!conn)
|
|
return;
|
|
if (!conn->msc_role) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR,
|
|
"Rx CO Initial message on conn that is not associated with any MSC role\n");
|
|
return;
|
|
}
|
|
|
|
|
|
an_apdu = (struct an_apdu){
|
|
.an_proto = rp->sri->ran->an_proto,
|
|
.msg = ctx->msg,
|
|
};
|
|
|
|
osmo_fsm_inst_dispatch(conn->msc_role, MSC_EV_FROM_RAN_COMPLETE_LAYER_3, &an_apdu);
|
|
return;
|
|
|
|
case RAN_PEER_EV_MSG_UP_CO:
|
|
ctx = data;
|
|
OSMO_ASSERT(ctx);
|
|
OSMO_ASSERT(ctx->conn);
|
|
OSMO_ASSERT(ctx->msg);
|
|
|
|
if (!ctx->conn->msc_role) {
|
|
LOG_RAN_PEER(rp, LOGL_ERROR,
|
|
"Rx CO message on conn that is not associated with any MSC role\n");
|
|
return;
|
|
}
|
|
|
|
an_apdu = (struct an_apdu){
|
|
.an_proto = rp->sri->ran->an_proto,
|
|
.msg = ctx->msg,
|
|
};
|
|
|
|
osmo_fsm_inst_dispatch(ctx->conn->msc_role, MSC_EV_FROM_RAN_UP_L2, &an_apdu);
|
|
return;
|
|
|
|
case RAN_PEER_EV_MSG_DOWN_CO_INITIAL:
|
|
ctx = data;
|
|
OSMO_ASSERT(ctx);
|
|
OSMO_ASSERT(ctx->msg);
|
|
sccp_ran_down_l2_co_initial(rp->sri, &rp->peer_addr, ctx->conn_id, ctx->msg);
|
|
return;
|
|
|
|
case RAN_PEER_EV_MSG_DOWN_CO:
|
|
ctx = data;
|
|
OSMO_ASSERT(ctx);
|
|
OSMO_ASSERT(ctx->msg);
|
|
sccp_ran_down_l2_co(rp->sri, ctx->conn_id, ctx->msg);
|
|
return;
|
|
|
|
case RAN_PEER_EV_MSG_DOWN_CL:
|
|
OSMO_ASSERT(data);
|
|
sccp_ran_down_l2_cl(rp->sri, &rp->peer_addr, (struct msgb*)data);
|
|
return;
|
|
|
|
case RAN_PEER_EV_RX_RESET:
|
|
msg = (struct msgb*)data;
|
|
ran_peer_rx_reset(rp, msg);
|
|
return;
|
|
|
|
default:
|
|
LOG_RAN_PEER(rp, LOGL_ERROR, "Unhandled event: %s\n", osmo_fsm_event_name(&ran_peer_fsm, event));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int ran_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct ran_peer *rp = fi->priv;
|
|
ran_peer_state_chg(rp, RAN_PEER_ST_WAIT_RX_RESET);
|
|
return 0;
|
|
}
|
|
|
|
void ran_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
|
{
|
|
struct ran_peer *rp = fi->priv;
|
|
ran_peer_discard_all_conns(rp);
|
|
llist_del(&rp->entry);
|
|
}
|
|
|
|
static const struct value_string ran_peer_fsm_event_names[] = {
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_MSG_UP_CL),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_MSG_UP_CO_INITIAL),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_MSG_UP_CO),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_MSG_DOWN_CL),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_MSG_DOWN_CO_INITIAL),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_MSG_DOWN_CO),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_RX_RESET),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_RX_RESET_ACK),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_CONNECTION_SUCCESS),
|
|
OSMO_VALUE_STRING(RAN_PEER_EV_CONNECTION_TIMEOUT),
|
|
{}
|
|
};
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
static const struct osmo_fsm_state ran_peer_fsm_states[] = {
|
|
[RAN_PEER_ST_WAIT_RX_RESET] = {
|
|
.name = "WAIT_RX_RESET",
|
|
.action = ran_peer_st_wait_rx_reset,
|
|
.in_event_mask = 0
|
|
| S(RAN_PEER_EV_RX_RESET)
|
|
| S(RAN_PEER_EV_MSG_UP_CO_INITIAL)
|
|
| S(RAN_PEER_EV_MSG_UP_CO)
|
|
| S(RAN_PEER_EV_CONNECTION_TIMEOUT)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(RAN_PEER_ST_WAIT_RX_RESET)
|
|
| S(RAN_PEER_ST_WAIT_RX_RESET_ACK)
|
|
| S(RAN_PEER_ST_READY)
|
|
| S(RAN_PEER_ST_DISCARDING)
|
|
,
|
|
},
|
|
[RAN_PEER_ST_WAIT_RX_RESET_ACK] = {
|
|
.name = "WAIT_RX_RESET_ACK",
|
|
.action = ran_peer_st_wait_rx_reset_ack,
|
|
.in_event_mask = 0
|
|
| S(RAN_PEER_EV_RX_RESET)
|
|
| S(RAN_PEER_EV_RX_RESET_ACK)
|
|
| S(RAN_PEER_EV_MSG_UP_CO_INITIAL)
|
|
| S(RAN_PEER_EV_MSG_UP_CO)
|
|
| S(RAN_PEER_EV_CONNECTION_TIMEOUT)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(RAN_PEER_ST_WAIT_RX_RESET)
|
|
| S(RAN_PEER_ST_WAIT_RX_RESET_ACK)
|
|
| S(RAN_PEER_ST_READY)
|
|
| S(RAN_PEER_ST_DISCARDING)
|
|
,
|
|
},
|
|
[RAN_PEER_ST_READY] = {
|
|
.name = "READY",
|
|
.action = ran_peer_st_ready,
|
|
.in_event_mask = 0
|
|
| S(RAN_PEER_EV_RX_RESET)
|
|
| S(RAN_PEER_EV_MSG_UP_CO_INITIAL)
|
|
| S(RAN_PEER_EV_MSG_UP_CO)
|
|
| S(RAN_PEER_EV_MSG_DOWN_CO_INITIAL)
|
|
| S(RAN_PEER_EV_MSG_DOWN_CO)
|
|
| S(RAN_PEER_EV_MSG_DOWN_CL)
|
|
,
|
|
.out_state_mask = 0
|
|
| S(RAN_PEER_ST_WAIT_RX_RESET)
|
|
| S(RAN_PEER_ST_WAIT_RX_RESET_ACK)
|
|
| S(RAN_PEER_ST_READY)
|
|
| S(RAN_PEER_ST_DISCARDING)
|
|
,
|
|
},
|
|
[RAN_PEER_ST_DISCARDING] = {
|
|
.name = "DISCARDING",
|
|
},
|
|
};
|
|
|
|
static struct osmo_fsm ran_peer_fsm = {
|
|
.name = "ran_peer",
|
|
.states = ran_peer_fsm_states,
|
|
.num_states = ARRAY_SIZE(ran_peer_fsm_states),
|
|
.log_subsys = DRR,
|
|
.event_names = ran_peer_fsm_event_names,
|
|
.timer_cb = ran_peer_fsm_timer_cb,
|
|
.cleanup = ran_peer_fsm_cleanup,
|
|
.allstate_action = ran_peer_allstate_action,
|
|
.allstate_event_mask = 0
|
|
| S(RAN_PEER_EV_MSG_UP_CL)
|
|
,
|
|
};
|
|
|
|
int ran_peer_up_l2(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *calling_addr, bool co, uint32_t conn_id,
|
|
struct msgb *l2)
|
|
{
|
|
struct ran_peer *ran_peer = NULL;
|
|
uint32_t event;
|
|
struct ran_peer_ev_ctx ctx = {
|
|
.conn_id = conn_id,
|
|
.msg = l2,
|
|
};
|
|
|
|
if (co) {
|
|
struct ran_conn *conn;
|
|
llist_for_each_entry(conn, &sri->ran_conns, entry) {
|
|
if (conn->sccp_conn_id == conn_id) {
|
|
ran_peer = conn->ran_peer;
|
|
ctx.conn = conn;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ran_peer && calling_addr) {
|
|
LOG_SCCP_RAN_CO(sri, calling_addr, conn_id, LOGL_ERROR,
|
|
"Connection-Oriented Initial message for already existing conn_id."
|
|
" Dropping message.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ran_peer && !calling_addr) {
|
|
LOG_SCCP_RAN_CO(sri, calling_addr, conn_id, LOGL_ERROR,
|
|
"Connection-Oriented non-Initial message for unknown conn_id %u."
|
|
" Dropping message.\n", conn_id);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (calling_addr) {
|
|
ran_peer = ran_peer_find_or_create(sri, calling_addr);
|
|
if (!ran_peer) {
|
|
LOG_SCCP_RAN_CL(sri, calling_addr, LOGL_ERROR, "Cannot register RAN peer\n");
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
OSMO_ASSERT(ran_peer && ran_peer->fi);
|
|
|
|
if (co)
|
|
event = calling_addr ? RAN_PEER_EV_MSG_UP_CO_INITIAL : RAN_PEER_EV_MSG_UP_CO;
|
|
else
|
|
event = RAN_PEER_EV_MSG_UP_CL;
|
|
|
|
return osmo_fsm_inst_dispatch(ran_peer->fi, event, &ctx);
|
|
}
|
|
|
|
void ran_peer_disconnect(struct sccp_ran_inst *sri, uint32_t conn_id)
|
|
{
|
|
struct ran_conn *conn;
|
|
llist_for_each_entry(conn, &sri->ran_conns, entry) {
|
|
if (conn->sccp_conn_id == conn_id) {
|
|
ran_conn_discard(conn);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ran_peer *ran_peer_find_by_cell_id(struct sccp_ran_inst *sri, const struct gsm0808_cell_id *cid,
|
|
bool expecting_single_match)
|
|
{
|
|
struct ran_peer *rp;
|
|
struct ran_peer *found = NULL;
|
|
|
|
llist_for_each_entry(rp, &sri->ran_peers, entry) {
|
|
if (cell_id_list_find(&rp->cells_seen, cid, 0, false)) {
|
|
if (!expecting_single_match)
|
|
return rp;
|
|
/* Otherwise continue iterating and log errors for multiple matches... */
|
|
if (found) {
|
|
LOG_RAN_PEER(found, LOGL_ERROR, "Cell appears in more than one RAN peer:"
|
|
" %s also appears in %s\n",
|
|
gsm0808_cell_id_name(cid), rp->fi->id);
|
|
} else
|
|
found = rp;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
struct ran_peer *ran_peer_find_by_addr(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *addr)
|
|
{
|
|
struct ran_peer *rp;
|
|
|
|
llist_for_each_entry(rp, &sri->ran_peers, entry) {
|
|
if (!osmo_sccp_addr_ri_cmp(addr, &rp->peer_addr))
|
|
return rp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int ran_peers_down_paging(struct sccp_ran_inst *sri, enum CELL_IDENT page_where, struct vlr_subscr *vsub,
|
|
enum paging_cause cause)
|
|
{
|
|
struct ran_peer *rp;
|
|
int ret = 0;
|
|
struct gsm0808_cell_id page_id;
|
|
gsm0808_cell_id_from_cgi(&page_id, page_where, &vsub->cgi);
|
|
|
|
switch (page_where) {
|
|
case CELL_IDENT_NO_CELL:
|
|
LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Asked to page on NO_CELL, which doesn't make sense.\n");
|
|
return 0;
|
|
|
|
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
|
|
case CELL_IDENT_UTRAN_RNC:
|
|
case CELL_IDENT_UTRAN_LAC_RNC:
|
|
LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Don't know how to page on %s\n",
|
|
gsm0808_cell_id_name(&page_id));
|
|
return 0;
|
|
|
|
default:
|
|
break;
|
|
};
|
|
|
|
llist_for_each_entry(rp, &sri->ran_peers, entry) {
|
|
ret += ran_peer_down_paging(rp, &page_id, vsub, cause);
|
|
}
|
|
|
|
if (!ret)
|
|
LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Paging failed, no RAN peers found for %s\n",
|
|
gsm0808_cell_id_name(&page_id));
|
|
return ret;
|
|
}
|
|
|
|
/* If the given vsub->cgi matches this ran_peer with respect to page_where, page and return 1.
|
|
* Otherwise return 0. (Return value: number of pagings sent) */
|
|
int ran_peer_down_paging(struct ran_peer *rp, const struct gsm0808_cell_id *page_id, struct vlr_subscr *vsub,
|
|
enum paging_cause cause)
|
|
{
|
|
struct msgb *l2;
|
|
|
|
/* There are also the RAN peers that are configured in the neighbor ident for Handover, but if those aren't
|
|
* connected, then we can't Page there. */
|
|
if (!cell_id_list_find(&rp->cells_seen, page_id, 0, false))
|
|
return 0;
|
|
|
|
LOG_RAN_PEER_CAT(rp, DPAG, LOGL_DEBUG, "Paging for %s on %s\n", vlr_subscr_name(vsub),
|
|
gsm0808_cell_id_name(page_id));
|
|
l2 = rp->sri->ran->sccp_ran_ops.make_paging_msg(rp->sri, page_id, vsub->imsi, vsub->tmsi, cause);
|
|
if (osmo_fsm_inst_dispatch(rp->fi, RAN_PEER_EV_MSG_DOWN_CL, l2)) {
|
|
/* Not allowed to send messages, the peer is not properly connected yet/anymore */
|
|
LOG_RAN_PEER_CAT(rp, DPAG, LOGL_ERROR,
|
|
"Paging for %s matched this RAN peer, but emitting a Paging failed\n",
|
|
gsm0808_cell_id_name(page_id));
|
|
msgb_free(l2);
|
|
return 0;
|
|
}
|
|
|
|
/* The RAN_PEER_EV_MSG_DOWN_CL handler calls sccp_ran_down_l2_cl(),
|
|
* which doesn't free msgb. We have to do this ourselves. */
|
|
msgb_free(l2);
|
|
|
|
return 1;
|
|
}
|