1113 lines
35 KiB
C
1113 lines
35 KiB
C
/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
* All Rights Reserved
|
|
*
|
|
* Author: Philipp Maier
|
|
*
|
|
* 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/mgcp_client/mgcp_client.h>
|
|
#include <osmocom/bsc/gsm_data.h>
|
|
#include <osmocom/bsc/osmo_bsc_mgcp.h>
|
|
#include <osmocom/bsc/debug.h>
|
|
#include <osmocom/bsc/osmo_bsc.h>
|
|
#include <osmocom/core/logging.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/core/timer.h>
|
|
#include <osmocom/core/fsm.h>
|
|
#include <osmocom/bsc/osmo_bsc_sigtran.h>
|
|
#include <osmocom/core/byteswap.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
#define MGCP_MGW_TIMEOUT 4 /* in seconds */
|
|
#define MGCP_MGW_TIMEOUT_TIMER_NR 1
|
|
#define MGCP_BSS_TIMEOUT 4 /* in seconds */
|
|
#define MGCP_BSS_TIMEOUT_TIMER_NR 2
|
|
|
|
#define MGCP_ENDPOINT_FORMAT "%x@mgw"
|
|
|
|
/* Some internal cause codes to indicate fault
|
|
* condition inside the FSM */
|
|
enum bsc_mgcp_cause_code {
|
|
MGCP_ERR_MGW_FAIL,
|
|
MGCP_ERR_MGW_INVAL_RESP,
|
|
MGCP_ERR_MGW_TX_FAIL,
|
|
MGCP_ERR_UNEXP_TEARDOWN,
|
|
MGCP_ERR_ASSGMNT_FAIL,
|
|
MGCP_ERR_UNSUPP_ADDR_FMT,
|
|
MGCP_ERR_BSS_TIMEOUT,
|
|
MGCP_ERR_NOMEM
|
|
};
|
|
|
|
/* Human readable respresentation of the faul codes,
|
|
* will be displayed by handle_error() */
|
|
static const struct value_string bsc_mgcp_cause_codes_names[] = {
|
|
{MGCP_ERR_MGW_FAIL, "operation failed on MGW"},
|
|
{MGCP_ERR_MGW_INVAL_RESP, "invalid / unparseable response from MGW"},
|
|
{MGCP_ERR_MGW_TX_FAIL, "failed to transmit MGCP message to MGW"},
|
|
{MGCP_ERR_UNEXP_TEARDOWN, "unexpected connection teardown (BSS)"},
|
|
{MGCP_ERR_ASSGMNT_FAIL, "assignment failure (BSS)"},
|
|
{MGCP_ERR_UNSUPP_ADDR_FMT, "unsupported network address format used (MSC)"},
|
|
{MGCP_ERR_BSS_TIMEOUT, "assignment could not be completed in time (BSS)"},
|
|
{MGCP_ERR_NOMEM, "out of memory"},
|
|
{0, NULL}
|
|
};
|
|
|
|
enum fsm_bsc_mgcp_states {
|
|
ST_CRCX_BTS,
|
|
ST_ASSIGN_PROC,
|
|
ST_MDCX_BTS,
|
|
ST_CRCX_NET,
|
|
ST_ASSIGN_COMPL,
|
|
ST_CALL,
|
|
ST_MDCX_BTS_HO,
|
|
ST_HALT
|
|
};
|
|
|
|
enum bsc_mgcp_fsm_evt {
|
|
/* Initial event: start off the state machine */
|
|
EV_INIT,
|
|
|
|
/* External event: Assignment complete, event is issued shortly before
|
|
* the assignment complete message is sent via the A-Interface */
|
|
EV_ASS_COMPLETE,
|
|
|
|
/* External event: Teardown event, this event is used to notify the end
|
|
* of a call. It is also issued in case of errors to teardown a half
|
|
* open connection. */
|
|
EV_TEARDOWN,
|
|
|
|
/* External event: Handover event, this event notifies the FSM that a
|
|
* handover is required. The FSM will then perform an extra MDCX to
|
|
* configure the new connection data at the MGW. The only valid state
|
|
* where a Handover event can be received is ST_CALL. */
|
|
EV_HANDOVER,
|
|
|
|
/* Internal event: The mgcp_gw has sent its CRCX response for
|
|
* the BTS side */
|
|
EV_CRCX_BTS_RESP,
|
|
|
|
/* Internal event: The mgcp_gw has sent its MDCX response for
|
|
* the BTS side */
|
|
EV_MDCX_BTS_RESP,
|
|
|
|
/* Internal event: The mgcp_gw has sent its CRCX response for
|
|
* the NET side */
|
|
EV_CRCX_NET_RESP,
|
|
|
|
/* Internal event: The mgcp_gw has sent its DLCX response for
|
|
* the NET and BTS side */
|
|
EV_DLCX_ALL_RESP,
|
|
|
|
/* Internal event: The mgcp_gw has responded to the (Handover-)
|
|
MDCX that has been send to update the BTS connection. */
|
|
EV_MDCX_BTS_HO_RESP,
|
|
};
|
|
|
|
/* A general error handler function. On error we still have an interest to
|
|
* remove a half open connection (if possible). This function will execute
|
|
* a controlled jump to the DLCX phase. From there, the FSM will then just
|
|
* continue like the call were ended normally */
|
|
static void handle_error(struct mgcp_ctx *mgcp_ctx, enum bsc_mgcp_cause_code cause)
|
|
{
|
|
struct osmo_fsm_inst *fi;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
fi = mgcp_ctx->fsm;
|
|
OSMO_ASSERT(fi);
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "%s -- graceful shutdown...\n",
|
|
get_value_string(bsc_mgcp_cause_codes_names, cause));
|
|
|
|
/* Set the VM into the state where it waits for the call end */
|
|
osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
|
|
|
|
/* Simulate the call end by sending a teardown event, so that
|
|
* the FSM proceeds directly with the DLCX */
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
|
|
}
|
|
|
|
static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv);
|
|
|
|
/* Callback for ST_CRCX_BTS: startup state machine send out CRCX for BTS side */
|
|
static void fsm_crcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = data;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct msgb *msg;
|
|
struct mgcp_msg mgcp_msg;
|
|
struct mgcp_client *mgcp;
|
|
uint16_t rtp_endpoint;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
|
|
rtp_endpoint = mgcp_client_next_endpoint(mgcp);
|
|
mgcp_ctx->rtp_endpoint = rtp_endpoint;
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG,
|
|
"CRCX/BTS: creating connection for the BTS side on MGW endpoint:0x%x...\n", rtp_endpoint);
|
|
|
|
/* Generate MGCP message string */
|
|
mgcp_msg = (struct mgcp_msg) {
|
|
.verb = MGCP_VERB_DLCX,
|
|
.presence = (MGCP_MSG_PRESENCE_ENDPOINT),
|
|
};
|
|
if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
|
|
MGCP_ENDPOINT_MAXLEN) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
|
|
return;
|
|
}
|
|
msg = mgcp_msg_gen(mgcp, &mgcp_msg);
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* Transmit MGCP message to MGW */
|
|
mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
|
|
rc = mgcp_client_tx(mgcp, msg, NULL, NULL);
|
|
|
|
|
|
/* Generate MGCP message string */
|
|
mgcp_msg = (struct mgcp_msg) {
|
|
.verb = MGCP_VERB_CRCX,
|
|
.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE),
|
|
.call_id = conn->conn_id,
|
|
.conn_mode = MGCP_CONN_LOOPBACK
|
|
};
|
|
if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
|
|
MGCP_ENDPOINT_MAXLEN) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
|
|
return;
|
|
}
|
|
msg = mgcp_msg_gen(mgcp, &mgcp_msg);
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* Transmit MGCP message to MGW */
|
|
mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
|
|
rc = mgcp_client_tx(mgcp, msg, crcx_for_bts_resp_cb, mgcp_ctx);
|
|
if (rc < 0) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
|
|
return;
|
|
}
|
|
|
|
osmo_fsm_inst_state_chg(fi, ST_ASSIGN_PROC, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
|
|
}
|
|
|
|
/* Callback for MGCP-Client: handle response for BTS associated CRCX */
|
|
static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = priv;
|
|
int rc;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
uint32_t addr;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR,
|
|
"CRCX/BTS: late MGW response, FSM already terminated -- ignoring...\n");
|
|
return;
|
|
}
|
|
|
|
if (r->head.response_code != 200) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
|
|
"CRCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment);
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
|
|
return;
|
|
}
|
|
|
|
/* memorize connection identifier */
|
|
osmo_strlcpy(mgcp_ctx->conn_id_bts, r->head.conn_id, sizeof(mgcp_ctx->conn_id_bts));
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with CI: %s\n", mgcp_ctx->conn_id_bts);
|
|
|
|
rc = mgcp_response_parse_params(r);
|
|
if (rc) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response\n");
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
|
|
return;
|
|
}
|
|
|
|
addr = inet_addr(r->audio_ip);
|
|
if (addr == INADDR_NONE) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response (invalid IP-address)\n");
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
|
|
|
|
/* Set the connection details in the conn struct. The code that
|
|
* controls the BTS via RSL will take these values and signal them
|
|
* to the BTS via RSL/IPACC */
|
|
conn->user_plane.rtp_port = r->audio_port;
|
|
conn->user_plane.rtp_ip = osmo_ntohl(addr);
|
|
|
|
/* Notify the FSM that we got the response. */
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_BTS_RESP, mgcp_ctx);
|
|
}
|
|
|
|
/* Callback for ST_ASSIGN_PROC: An mgcp response has been received, proceed
|
|
* with the assignment request */
|
|
static void fsm_proc_assignmnent_req_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = data;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
enum gsm48_chan_mode chan_mode;
|
|
bool full_rate;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
|
|
switch (event) {
|
|
case EV_CRCX_BTS_RESP:
|
|
break;
|
|
default:
|
|
handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
|
|
return;
|
|
}
|
|
|
|
OSMO_ASSERT(conn->conn);
|
|
chan_mode = mgcp_ctx->chan_mode;
|
|
full_rate = mgcp_ctx->full_rate;
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "MGW proceeding assignment request...\n");
|
|
rc = gsm0808_assign_req(conn->conn, chan_mode, full_rate);
|
|
|
|
if (rc < 0) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
|
|
return;
|
|
}
|
|
|
|
osmo_fsm_inst_state_chg(fi, ST_MDCX_BTS, MGCP_BSS_TIMEOUT, MGCP_BSS_TIMEOUT_TIMER_NR);
|
|
}
|
|
|
|
static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv);
|
|
|
|
/* Callback for ST_MDCX_BTS: When the BSS has completed the assignment,
|
|
* proceed with updating the connection for the BTS side */
|
|
static void fsm_mdcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = data;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct gsm_lchan *lchan;
|
|
struct msgb *msg;
|
|
struct mgcp_msg mgcp_msg;
|
|
struct mgcp_client *mgcp;
|
|
uint16_t rtp_endpoint;
|
|
struct in_addr addr;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
|
|
switch (event) {
|
|
case EV_ASS_COMPLETE:
|
|
break;
|
|
default:
|
|
handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
|
|
return;
|
|
}
|
|
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
lchan = mgcp_ctx->lchan;
|
|
OSMO_ASSERT(lchan);
|
|
|
|
rtp_endpoint = mgcp_ctx->rtp_endpoint;
|
|
|
|
addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
|
|
LOGPFSML(fi, LOGL_DEBUG,
|
|
"MDCX/BTS: completing connection for the BTS side on MGW endpoint:0x%x, BTS expects RTP input on address %s:%u\n",
|
|
rtp_endpoint, inet_ntoa(addr), lchan->abis_ip.bound_port);
|
|
|
|
/* Generate MGCP message string */
|
|
mgcp_msg = (struct mgcp_msg) {
|
|
.verb = MGCP_VERB_MDCX,
|
|
.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
|
|
MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
|
|
.call_id = conn->conn_id,
|
|
.conn_id = mgcp_ctx->conn_id_bts,
|
|
.conn_mode = MGCP_CONN_RECV_SEND,
|
|
.audio_ip = inet_ntoa(addr),
|
|
.audio_port = lchan->abis_ip.bound_port
|
|
};
|
|
if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
|
|
sizeof(mgcp_msg.endpoint)) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
|
|
return;
|
|
}
|
|
msg = mgcp_msg_gen(mgcp, &mgcp_msg);
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* Transmit MGCP message to MGW */
|
|
mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
|
|
rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_resp_cb, mgcp_ctx);
|
|
if (rc < 0) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
|
|
return;
|
|
}
|
|
|
|
osmo_fsm_inst_state_chg(fi, ST_CRCX_NET, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
|
|
}
|
|
|
|
/* Callback for MGCP-Client: handle response for BTS associated MDCX */
|
|
static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = priv;
|
|
int rc;
|
|
struct in_addr addr;
|
|
struct gsm_lchan *lchan;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
lchan = mgcp_ctx->lchan;
|
|
OSMO_ASSERT(lchan);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR,
|
|
"MDCX/BTS: late MGW response, FSM already terminated -- ignoring...\n");
|
|
return;
|
|
}
|
|
|
|
if (r->head.response_code != 200) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
|
|
"MDCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment);
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
|
|
return;
|
|
}
|
|
|
|
rc = mgcp_response_parse_params(r);
|
|
if (rc) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "MDCX/BTS: Cannot parse MDCX response\n");
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
|
|
|
|
addr.s_addr = lchan->abis_ip.bound_ip;
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
|
|
"MDCX/BTS: corresponding lchan has been bound to address %s:%u\n",
|
|
inet_ntoa(addr), lchan->abis_ip.bound_port);
|
|
|
|
/* Notify the FSM that we got the response. */
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_RESP, mgcp_ctx);
|
|
}
|
|
|
|
static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv);
|
|
|
|
/* Callback for ST_CRCX_NET: An mgcp response has been received, proceed... */
|
|
static void fsm_crcx_net_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = data;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct msgb *msg;
|
|
struct mgcp_msg mgcp_msg;
|
|
struct mgcp_client *mgcp;
|
|
uint16_t rtp_endpoint;
|
|
struct sockaddr_in *sin;
|
|
char *addr;
|
|
uint16_t port;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
|
|
switch (event) {
|
|
case EV_MDCX_BTS_RESP:
|
|
break;
|
|
default:
|
|
handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
|
|
return;
|
|
}
|
|
|
|
rtp_endpoint = mgcp_ctx->rtp_endpoint;
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG,
|
|
"CRCX/NET: creating connection for the NET side on MGW endpoint:0x%x...\n", rtp_endpoint);
|
|
|
|
/* Currently we only have support for IPv4 in our MGCP software, the
|
|
* AoIP part is ready to support IPv6 in theory, because the IE
|
|
* parser/generator uses sockaddr_storage for the AoIP transport
|
|
* identifier. However, the MGW does not support IPv6 yet. This is
|
|
* why we stop here in case some MSC tries to signal IPv6 AoIP
|
|
* transport identifiers */
|
|
if (conn->user_plane.aoip_rtp_addr_remote.ss_family != AF_INET) {
|
|
LOGPFSML(fi, LOGL_ERROR,
|
|
"CRCX/NET: endpoint:%x MSC uses unsupported address format in AoIP transport identifier -- aborting...\n",
|
|
rtp_endpoint);
|
|
handle_error(mgcp_ctx, MGCP_ERR_UNSUPP_ADDR_FMT);
|
|
return;
|
|
}
|
|
|
|
sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote;
|
|
addr = inet_ntoa(sin->sin_addr);
|
|
port = osmo_ntohs(sin->sin_port);
|
|
LOGPFSML(fi, LOGL_DEBUG, "CRCX/NET: MSC expects RTP input on address %s:%u\n", addr, port);
|
|
|
|
/* Generate MGCP message string */
|
|
mgcp_msg = (struct mgcp_msg) {
|
|
.verb = MGCP_VERB_CRCX,
|
|
.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE |
|
|
MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
|
|
.call_id = conn->conn_id,
|
|
.conn_mode = MGCP_CONN_RECV_SEND,
|
|
.audio_ip = addr,
|
|
.audio_port = port
|
|
};
|
|
if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
|
|
sizeof(mgcp_msg.endpoint)) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
|
|
return;
|
|
}
|
|
msg = mgcp_msg_gen(mgcp, &mgcp_msg);
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* Transmit MGCP message to MGW */
|
|
mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
|
|
rc = mgcp_client_tx(mgcp, msg, crcx_for_net_resp_cb, mgcp_ctx);
|
|
if (rc < 0) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
|
|
return;
|
|
}
|
|
|
|
osmo_fsm_inst_state_chg(fi, ST_ASSIGN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
|
|
}
|
|
|
|
/* Callback for MGCP-Client: handle response for NET associated CRCX */
|
|
static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = priv;
|
|
int rc;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct gsm_lchan *lchan;
|
|
struct sockaddr_in *sin;
|
|
uint32_t addr;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
lchan = mgcp_ctx->lchan;
|
|
OSMO_ASSERT(lchan);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR,
|
|
"CRCX/NET: late MGW response, FSM already terminated -- ignoring...\n");
|
|
return;
|
|
}
|
|
|
|
if (r->head.response_code != 200) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
|
|
"CRCX/NET: response yields error: %d %s\n", r->head.response_code, r->head.comment);
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
|
|
return;
|
|
}
|
|
|
|
/* memorize connection identifier */
|
|
osmo_strlcpy(mgcp_ctx->conn_id_net, r->head.conn_id, sizeof(mgcp_ctx->conn_id_net));
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with CI: %s\n", mgcp_ctx->conn_id_net);
|
|
|
|
rc = mgcp_response_parse_params(r);
|
|
if (rc) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse CRCX response\n");
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
|
|
return;
|
|
}
|
|
|
|
addr = inet_addr(r->audio_ip);
|
|
if (addr == INADDR_NONE) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse response (invalid IP-address)\n");
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with address %s:%u\n",
|
|
r->audio_ip, r->audio_port);
|
|
|
|
/* Store address */
|
|
sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local;
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = addr;
|
|
sin->sin_port = osmo_ntohs(r->audio_port);
|
|
|
|
/* Notify the FSM that we got the response. */
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_NET_RESP, mgcp_ctx);
|
|
}
|
|
|
|
/* Callback for ST_ASSIGN_COMPL: Send back assignment complete and wait until the call ends */
|
|
static void fsm_send_assignment_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = data;
|
|
struct gsm_lchan *lchan;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
|
|
switch (event) {
|
|
case EV_CRCX_NET_RESP:
|
|
break;
|
|
default:
|
|
handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
|
|
return;
|
|
}
|
|
|
|
lchan = mgcp_ctx->lchan;
|
|
OSMO_ASSERT(lchan);
|
|
|
|
/* Send assignment completion message via AoIP, this will complete
|
|
* the circuit. The message will also contain the port and IP-Address
|
|
* where the MGW expects the RTP input from the MSC side */
|
|
bssmap_send_aoip_ass_compl(lchan);
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "call in progress, waiting for call end...\n");
|
|
|
|
osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
|
|
}
|
|
|
|
static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv);
|
|
static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv);
|
|
|
|
/* Helper function to perform a connection teardown. This function may be
|
|
* called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state
|
|
* change to ST_HALT when teardown is done. */
|
|
static void handle_teardown(struct mgcp_ctx *mgcp_ctx)
|
|
{
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct msgb *msg;
|
|
struct mgcp_msg mgcp_msg;
|
|
struct mgcp_client *mgcp;
|
|
uint16_t rtp_endpoint;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
|
|
rtp_endpoint = mgcp_ctx->rtp_endpoint;
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
|
|
"DLCX: removing connection for the BTS and NET side on MGW endpoint:0x%x...\n", rtp_endpoint);
|
|
|
|
/* We now relase the endpoint back to the pool in order to allow
|
|
* other connections to use this endpoint */
|
|
mgcp_client_release_endpoint(rtp_endpoint, mgcp);
|
|
|
|
/* Generate MGCP message string */
|
|
mgcp_msg = (struct mgcp_msg) {
|
|
.verb = MGCP_VERB_DLCX,
|
|
.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID),
|
|
.call_id = conn->conn_id
|
|
};
|
|
if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
|
|
sizeof(mgcp_msg.endpoint)) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
|
|
return;
|
|
}
|
|
msg = mgcp_msg_gen(mgcp, &mgcp_msg);
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* Transmit MGCP message to MGW */
|
|
mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
|
|
rc = mgcp_client_tx(mgcp, msg, dlcx_for_all_resp_cb, mgcp_ctx);
|
|
if (rc < 0) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
|
|
return;
|
|
}
|
|
|
|
osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_HALT, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
|
|
}
|
|
|
|
/* Helper function to perform a handover (MDCX). This function may be
|
|
* called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state
|
|
* change to ST_CALL when teardown is done. */
|
|
static void handle_handover(struct mgcp_ctx *mgcp_ctx)
|
|
{
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct msgb *msg;
|
|
struct mgcp_msg mgcp_msg;
|
|
struct mgcp_client *mgcp;
|
|
struct gsm_lchan *ho_lchan;
|
|
uint16_t rtp_endpoint;
|
|
struct in_addr addr;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
ho_lchan = mgcp_ctx->ho_lchan;
|
|
OSMO_ASSERT(ho_lchan);
|
|
|
|
rtp_endpoint = mgcp_ctx->rtp_endpoint;
|
|
|
|
addr.s_addr = osmo_ntohl(ho_lchan->abis_ip.bound_ip);
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
|
|
"MDCX/BTS/HO: handover connection from old BTS to new BTS side on MGW endpoint:0x%x, new BTS expects RTP input on address %s:%u\n\n",
|
|
rtp_endpoint, inet_ntoa(addr), ho_lchan->abis_ip.bound_port);
|
|
|
|
/* Generate MGCP message string */
|
|
mgcp_msg = (struct mgcp_msg) {
|
|
.verb = MGCP_VERB_MDCX,
|
|
.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
|
|
MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |
|
|
MGCP_MSG_PRESENCE_AUDIO_PORT),
|
|
.call_id = conn->conn_id,
|
|
.conn_id = mgcp_ctx->conn_id_bts,
|
|
.conn_mode = MGCP_CONN_RECV_SEND,
|
|
.audio_ip = inet_ntoa(addr),
|
|
.audio_port = ho_lchan->abis_ip.bound_port};
|
|
if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
|
|
sizeof(mgcp_msg.endpoint)) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
|
|
return;
|
|
}
|
|
msg = mgcp_msg_gen(mgcp, &mgcp_msg);
|
|
OSMO_ASSERT(msg);
|
|
|
|
/* Transmit MGCP message to MGW */
|
|
mgcp_ctx->mgw_pending_trans = mgcp_msg_trans_id(msg);
|
|
rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_ho_resp_cb, mgcp_ctx);
|
|
if (rc < 0) {
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
|
|
return;
|
|
}
|
|
|
|
osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
|
|
}
|
|
|
|
/* Callback for ST_CALL: Handle call teardown and Handover */
|
|
static void fsm_active_call_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = data;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
|
|
switch (event) {
|
|
case EV_TEARDOWN:
|
|
handle_teardown(mgcp_ctx);
|
|
break;
|
|
case EV_HANDOVER:
|
|
handle_handover(mgcp_ctx);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/* Callback for MGCP-Client: handle response for BTS/Handover associated MDCX */
|
|
static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = priv;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR, "MDCX/BTS/HO: late MGW response, FSM already terminated -- ignoring...\n");
|
|
return;
|
|
}
|
|
|
|
if (r->head.response_code != 200) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
|
|
"MDCX/BTS/HO: response yields error: %d %s\n", r->head.response_code, r->head.comment);
|
|
handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
|
|
return;
|
|
}
|
|
|
|
/* Notify the FSM that we got the response. */
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_HO_RESP, mgcp_ctx);
|
|
}
|
|
|
|
/* Callback for ST_MDCX_BTS_HO: Complete updating the connection data after
|
|
* handoverin the call to another BTS */
|
|
static void fsm_complete_handover(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
|
|
switch (event) {
|
|
case EV_MDCX_BTS_HO_RESP:
|
|
/* The response from the MGW arrived, the connection pointing
|
|
* towards the BTS is now updated, so we now change back to
|
|
* ST_CALL, where we will wait for the call-end (or another
|
|
* handover) */
|
|
LOGPFSML(fi, LOGL_DEBUG, "MDCX/BTS/HO: handover done, waiting for call end...\n");
|
|
osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
|
|
break;
|
|
case EV_HANDOVER:
|
|
/* This handles the rare, but possible situation where another
|
|
* handover is happening while we still wait for the the MGW to
|
|
* complete the current one. In this case we will stop waiting
|
|
* for the response and directly move on with that second
|
|
* handover */
|
|
handle_handover(mgcp_ctx);
|
|
break;
|
|
case EV_TEARDOWN:
|
|
/* It may happen that the BSS wants to teardown all connections
|
|
* while we are still waiting for the MGW to respond. In this
|
|
* case we start to teard down the connection immediately */
|
|
handle_teardown(mgcp_ctx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Callback for MGCP-Client: handle response for NET associated CRCX */
|
|
static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = priv;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
struct mgcp_client *mgcp;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR,
|
|
"DLCX: late MGW response, FSM already terminated -- ignoring...\n");
|
|
return;
|
|
}
|
|
|
|
/* Note: We check the return code, but in case of an error there is
|
|
* not much that can be done to recover. However, at least we tryed
|
|
* to remove the connection (if there was even any) */
|
|
if (r->head.response_code != 200) {
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
|
|
"DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "DLCX: MGW has acknowledged the removal of the connections\n");
|
|
|
|
/* Notify the FSM that we got the response. */
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_DLCX_ALL_RESP, mgcp_ctx);
|
|
}
|
|
|
|
/* Callback for ST_HALT: Terminate the state machine */
|
|
static void fsm_halt_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
|
|
struct osmo_bsc_sccp_con *conn;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
|
|
/* Send pending sigtran message */
|
|
if (mgcp_ctx->resp) {
|
|
LOGPFSML(fi, LOGL_DEBUG, "sending pending sigtran response message...\n");
|
|
osmo_bsc_sigtran_send(conn, mgcp_ctx->resp);
|
|
mgcp_ctx->resp = NULL;
|
|
}
|
|
|
|
LOGPFSML(fi, LOGL_DEBUG, "state machine halted\n");
|
|
|
|
/* Destroy the state machine and all context information */
|
|
osmo_fsm_inst_free(mgcp_ctx->fsm);
|
|
mgcp_ctx->fsm = NULL;
|
|
}
|
|
|
|
/* Timer callback to shut down in case of connectivity problems */
|
|
static int fsm_timeout_cb(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx = fi->priv;
|
|
struct mgcp_client *mgcp;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
mgcp = mgcp_ctx->mgcp;
|
|
OSMO_ASSERT(mgcp);
|
|
|
|
/* Ensure that no sigtran response, is present. Otherwiese we might try
|
|
* to send a sigtran response when the sccp connection is already freed. */
|
|
mgcp_ctx->resp = NULL;
|
|
|
|
if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
|
|
/* Note: We were unable to communicate with the MGW,
|
|
* unfortunately there is no meaningful action we can take
|
|
* now other than giving up. */
|
|
|
|
/* At least release the occupied endpoint ID */
|
|
mgcp_client_release_endpoint(mgcp_ctx->rtp_endpoint, mgcp);
|
|
|
|
/* Cancel the transaction that timed out */
|
|
mgcp_client_cancel(mgcp, mgcp_ctx->mgw_pending_trans);
|
|
|
|
/* Initiate self destruction of the FSM */
|
|
osmo_fsm_inst_state_chg(fi, ST_HALT, 0, 0);
|
|
osmo_fsm_inst_dispatch(fi, EV_TEARDOWN, mgcp_ctx);
|
|
} else if (fi->T == MGCP_BSS_TIMEOUT_TIMER_NR)
|
|
/* Note: If the logic that controls the BSS is unable to
|
|
* negotiate a connection, we presumably still have a
|
|
* working connection to the MGW, we will try to
|
|
* shut down gracefully. */
|
|
handle_error(mgcp_ctx, MGCP_ERR_BSS_TIMEOUT);
|
|
else {
|
|
/* Note: Ther must not be any unsolicited timers
|
|
* in this FSM. If so, we have serious problem. */
|
|
OSMO_ASSERT(false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct osmo_fsm_state fsm_bsc_mgcp_states[] = {
|
|
|
|
/* Startup state machine, send CRCX to BTS. */
|
|
[ST_CRCX_BTS] = {
|
|
.in_event_mask = S(EV_INIT),
|
|
.out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_ASSIGN_PROC),
|
|
.name = OSMO_STRINGIFY(ST_CRCX_BTS),
|
|
.action = fsm_crcx_bts_cb,
|
|
},
|
|
|
|
/* When the CRCX response for the BTS side is received, then
|
|
* proceed the assignment on the BSS side. */
|
|
[ST_ASSIGN_PROC] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_CRCX_BTS_RESP),
|
|
.out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_BTS),
|
|
.name = OSMO_STRINGIFY(ST_ASSIGN_PROC),
|
|
.action = fsm_proc_assignmnent_req_cb,
|
|
},
|
|
|
|
/* When the BSS has processed the assignment request,
|
|
* then send the MDCX command for the BTS side in order to
|
|
* update the connections with the actual PORT/IP where the
|
|
* BTS expects the RTP input. */
|
|
[ST_MDCX_BTS] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_ASS_COMPLETE),
|
|
.out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_NET),
|
|
.name = OSMO_STRINGIFY(ST_MDCX_BTS),
|
|
.action = fsm_mdcx_bts_cb,
|
|
},
|
|
|
|
/* When the MDCX response for the BTS siede is received, then
|
|
* directly proceed with sending the CRCX command to connect the
|
|
* network side. This is done in one phase (no MDCX needed). */
|
|
[ST_CRCX_NET] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_MDCX_BTS_RESP),
|
|
.out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_ASSIGN_COMPL),
|
|
.name = OSMO_STRINGIFY(ST_CRCX_NET),
|
|
.action = fsm_crcx_net_cb,
|
|
},
|
|
|
|
/* When the CRCX response for the NET side is received. Then
|
|
* send the assignment complete message via the A-Interface and
|
|
* enter wait state in order to wait for the end of the call. */
|
|
[ST_ASSIGN_COMPL] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_CRCX_NET_RESP),
|
|
.out_state_mask = S(ST_HALT) | S(ST_CALL),
|
|
.name = OSMO_STRINGIFY(ST_ASSIGN_COMPL),
|
|
.action = fsm_send_assignment_complete,
|
|
},
|
|
|
|
/* When the call ends, remove all RTP connections from the
|
|
* MGW by sending a wildcarded DLCX. In case of a handover,
|
|
* go for an extra MDCX to update the connection and land in
|
|
* this state again when done. */
|
|
[ST_CALL] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_HANDOVER),
|
|
.out_state_mask = S(ST_HALT) | S(ST_MDCX_BTS_HO),
|
|
.name = OSMO_STRINGIFY(ST_CALL),
|
|
.action = fsm_active_call_cb,
|
|
},
|
|
|
|
/* A handover is in progress. When the response to the respective
|
|
* MDCX is received, then go back to ST_CALL and wait for the
|
|
* call end */
|
|
[ST_MDCX_BTS_HO] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_HANDOVER) | S(EV_MDCX_BTS_HO_RESP),
|
|
.out_state_mask = S(ST_HALT) | S(ST_CALL),
|
|
.name = OSMO_STRINGIFY(ST_MDCX_BTS_HO),
|
|
.action = fsm_complete_handover,
|
|
},
|
|
|
|
/* When the MGW confirms that the connections are terminated,
|
|
* then halt the state machine. */
|
|
[ST_HALT] = {
|
|
.in_event_mask = S(EV_TEARDOWN) | S(EV_DLCX_ALL_RESP),
|
|
.out_state_mask = 0,
|
|
.name = OSMO_STRINGIFY(ST_HALT),
|
|
.action = fsm_halt_cb,
|
|
},
|
|
};
|
|
|
|
/* State machine definition */
|
|
static struct osmo_fsm fsm_bsc_mgcp = {
|
|
.name = "MGW",
|
|
.states = fsm_bsc_mgcp_states,
|
|
.num_states = ARRAY_SIZE(fsm_bsc_mgcp_states),
|
|
.log_subsys = DMGCP,
|
|
.timer_cb = fsm_timeout_cb,
|
|
};
|
|
|
|
/* Notify that the a new call begins. This will create a connection for the
|
|
* BTS on the MGW and set up the port numbers in struct osmo_bsc_sccp_con.
|
|
* After that gsm0808_assign_req() to proceed.
|
|
* Parameter:
|
|
* ctx: talloc context
|
|
* network: associated gsm network
|
|
* conn: associated sccp connection
|
|
* chan_mode: channel mode (system data, passed through)
|
|
* full_rate: full rate flag (system data, passed through)
|
|
* Returns an mgcp_context that contains system data and the OSMO-FSM */
|
|
struct mgcp_ctx *mgcp_assignm_req(void *ctx, struct mgcp_client *mgcp, struct osmo_bsc_sccp_con *conn,
|
|
enum gsm48_chan_mode chan_mode, bool full_rate)
|
|
{
|
|
struct mgcp_ctx *mgcp_ctx;
|
|
char name[32];
|
|
static bool fsm_registered = false;
|
|
|
|
OSMO_ASSERT(mgcp);
|
|
OSMO_ASSERT(conn);
|
|
|
|
OSMO_ASSERT(snprintf(name, sizeof(name), "MGW_%i", conn->conn_id) < sizeof(name));
|
|
|
|
/* Register the fsm description (if not already done) */
|
|
if (fsm_registered == false) {
|
|
osmo_fsm_register(&fsm_bsc_mgcp);
|
|
fsm_registered = true;
|
|
}
|
|
|
|
/* Allocate and configure a new fsm instance */
|
|
mgcp_ctx = talloc_zero(ctx, struct mgcp_ctx);
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
|
|
mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm_bsc_mgcp, NULL, ctx, LOGL_DEBUG, name);
|
|
OSMO_ASSERT(mgcp_ctx->fsm);
|
|
mgcp_ctx->fsm->priv = mgcp_ctx;
|
|
mgcp_ctx->mgcp = mgcp;
|
|
mgcp_ctx->conn = conn;
|
|
mgcp_ctx->chan_mode = chan_mode;
|
|
mgcp_ctx->full_rate = full_rate;
|
|
|
|
/* start state machine */
|
|
OSMO_ASSERT(mgcp_ctx->fsm->state == ST_CRCX_BTS);
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_INIT, mgcp_ctx);
|
|
|
|
return mgcp_ctx;
|
|
}
|
|
|
|
/* Notify that the call has ended, remove all connections from the MGW,
|
|
* then send the clear complete message and destroy the FSM instance
|
|
* Parameter:
|
|
* mgcp_ctx: context information (FSM, and pointer to external system data)
|
|
* respmgcp_ctx: pending clear complete message to send via A-Interface */
|
|
void mgcp_clear_complete(struct mgcp_ctx *mgcp_ctx, struct msgb *resp)
|
|
{
|
|
struct osmo_bsc_sccp_con *conn;
|
|
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
OSMO_ASSERT(resp);
|
|
conn = mgcp_ctx->conn;
|
|
OSMO_ASSERT(conn);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR,
|
|
"clear completion attemted on already terminated FSM -- forwarding directly...\n");
|
|
osmo_bsc_sigtran_send(conn, resp);
|
|
mgcp_ctx->resp = NULL;
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating call end...\n");
|
|
|
|
mgcp_ctx->resp = resp;
|
|
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
|
|
}
|
|
|
|
/* Notify that the BSS ready, send the assingnment complete message when the
|
|
* mgcp connection is completed
|
|
* Parameter:
|
|
* mgcp_ctx: context information (FSM, and pointer to external system data)
|
|
* lchan: needed for sending the assignment complete message via A-Interface */
|
|
void mgcp_ass_complete(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *lchan)
|
|
{
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
OSMO_ASSERT(lchan);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR, "assignment completion attemted on already terminated FSM -- ignored\n");
|
|
mgcp_ctx->lchan = NULL;
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating assignment completion...\n");
|
|
|
|
mgcp_ctx->lchan = lchan;
|
|
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASS_COMPLETE, mgcp_ctx);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Notify that the call got handovered to another BTS, update the connection
|
|
* that is pointing to the BTS side with the connection data for the new bts.
|
|
* Parameter:
|
|
* mgcp_ctx: context information (FSM, and pointer to external system data)
|
|
* ho_lchan: the lchan on the new BTS */
|
|
void mgcp_handover(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *ho_lchan)
|
|
{
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
OSMO_ASSERT(ho_lchan);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_ERROR, "handover attemted on already terminated FSM -- ignored\n");
|
|
mgcp_ctx->ho_lchan = NULL;
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating handover...\n");
|
|
|
|
mgcp_ctx->ho_lchan = ho_lchan;
|
|
|
|
osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_HANDOVER, mgcp_ctx);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Free an existing mgcp context gracefully
|
|
* Parameter:
|
|
* mgcp_ctx: context information (FSM, and pointer to external system data) */
|
|
void mgcp_free_ctx(struct mgcp_ctx *mgcp_ctx)
|
|
{
|
|
OSMO_ASSERT(mgcp_ctx);
|
|
|
|
if (mgcp_ctx->fsm == NULL) {
|
|
LOGP(DMGCP, LOGL_DEBUG, "fsm already terminated, freeing only related context information...\n");
|
|
talloc_free(mgcp_ctx);
|
|
return;
|
|
}
|
|
|
|
LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "terminating fsm and freeing related context information...\n");
|
|
|
|
osmo_fsm_inst_free(mgcp_ctx->fsm);
|
|
talloc_free(mgcp_ctx);
|
|
}
|