diff options
Diffstat (limited to 'src/osmo-bsc/osmo_bsc_mgcp.c')
-rw-r--r-- | src/osmo-bsc/osmo_bsc_mgcp.c | 1146 |
1 files changed, 0 insertions, 1146 deletions
diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c deleted file mode 100644 index db649c791..000000000 --- a/src/osmo-bsc/osmo_bsc_mgcp.c +++ /dev/null @@ -1,1146 +0,0 @@ -/* (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/bsc/signal.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/bsc/bsc_subscr_conn_fsm.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 */ -#define handle_error(mgcp_ctx, cause) \ - _handle_error(mgcp_ctx, cause, __FILE__, __LINE__) -static void _handle_error(struct mgcp_ctx *mgcp_ctx, enum bsc_mgcp_cause_code cause, - const char *file, int line) -{ - struct osmo_fsm_inst *fi; - - OSMO_ASSERT(mgcp_ctx); - fi = mgcp_ctx->fsm; - OSMO_ASSERT(fi); - - LOGPFSMLSRC(mgcp_ctx->fsm, LOGL_ERROR, file, line, "%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 gsm_subscriber_connection *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_CRCX, - .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE), - .call_id = conn->sccp.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 gsm_subscriber_connection *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 gsm_subscriber_connection *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); - chan_mode = mgcp_ctx->chan_mode; - full_rate = mgcp_ctx->full_rate; - - LOGPFSML(fi, LOGL_DEBUG, "MGW proceeding assignment request...\n"); - uint32_t param = (full_rate << 16) | chan_mode; - osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_ASSIGNMENT_CMD, ¶m); - - 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 gsm_subscriber_connection *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->sccp.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 gsm_subscriber_connection *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->sccp.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 gsm_subscriber_connection *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 gsm_subscriber_connection *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->sccp.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 gsm_subscriber_connection *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->sccp.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 gsm_subscriber_connection *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 gsm_subscriber_connection *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; - } - - /* 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 gsm_subscriber_connection *conn, - enum gsm48_chan_mode chan_mode, bool full_rate) -{ - struct mgcp_ctx *mgcp_ctx; - char name[32]; - - OSMO_ASSERT(mgcp); - OSMO_ASSERT(conn); - - OSMO_ASSERT(snprintf(name, sizeof(name), "MGW_%i", conn->sccp.conn_id) < sizeof(name)); - - /* 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 gsm_subscriber_connection *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 */ -static 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; -} - -/* GSM 08.58 HANDOVER DETECT has been received */ -static int mgcp_sig_ho_detect(struct gsm_lchan *new_lchan) -{ - struct gsm_subscriber_connection *conn; - if (!new_lchan) { - LOGP(DHO, LOGL_ERROR, "HO Detect signal for NULL lchan\n"); - return -EINVAL; - } - - conn = new_lchan->conn; - - if (!conn) { - LOGP(DHO, LOGL_ERROR, "%s HO Detect for lchan without conn\n", - gsm_lchan_name(new_lchan)); - return -EINVAL; - } - - if (!conn->sccp.conn_id) { - LOGP(DHO, LOGL_ERROR, "%s HO Detect for conn without sccp_conn_id\n", - gsm_lchan_name(new_lchan)); - return -EINVAL; - } - - if (!conn->user_plane.mgcp_ctx) { - LOGP(DHO, LOGL_ERROR, "%s HO Detect for conn without MGCP ctx\n", - gsm_lchan_name(new_lchan)); - return -EINVAL; - } - - mgcp_handover(conn->user_plane.mgcp_ctx, new_lchan); - return 0; -} - -static int mgcp_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - struct lchan_signal_data *lchan_data; - - switch (subsys) { - case SS_LCHAN: - lchan_data = signal_data; - switch (signal) { - case S_LCHAN_HANDOVER_DETECT: - return mgcp_sig_ho_detect(lchan_data->lchan); - } - break; - default: - break; - } - - return 0; -} - -/* 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); -} - -void mgcp_init(struct gsm_network *net) -{ - osmo_fsm_register(&fsm_bsc_mgcp); - osmo_signal_register_handler(SS_LCHAN, mgcp_sig_cb, NULL); -} |