aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc/msc_mgcp.c
diff options
context:
space:
mode:
authorPhilipp Maier <pmaier@sysmocom.de>2017-11-07 17:19:25 +0100
committerHarald Welte <laforge@gnumonks.org>2018-02-05 22:28:43 +0000
commit621ba032bdf60383f5431ca936040c620551a3d5 (patch)
tree0fd222b08503b64e6d4109a7f3c0b678b16bf1ec /src/libmsc/msc_mgcp.c
parent64dbc5464c7ef8f0ba2acbb1c924d4eef3e6e72e (diff)
mgcp: use osmo-mgw to switch rtp streams
in the current implementation we still use osmo-bsc_mgcp, which has many problems and is also obsoleted by osmo-mgw. integrate osmo-mgw and re-implement the current switching using an osmo fsm. Depends: osmo-mgw Iab6a6038e7610c62f34e642cd49c93d11151252c Depends: osmo-iuh I3c1a0455c5f25cae41ee19229d6daf299e023062 Closes: OS#2605 Change-Id: Ieea9630358b3963261fa1993cf1f3b563ff23538
Diffstat (limited to 'src/libmsc/msc_mgcp.c')
-rw-r--r--src/libmsc/msc_mgcp.c1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/src/libmsc/msc_mgcp.c b/src/libmsc/msc_mgcp.c
new file mode 100644
index 000000000..bd60c1328
--- /dev/null
+++ b/src/libmsc/msc_mgcp.c
@@ -0,0 +1,1078 @@
+/* (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 <arpa/inet.h>
+
+#include <osmocom/mgcp_client/mgcp_client.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/msc/msc_mgcp.h>
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/a_iface.h>
+#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/gsm_04_08.h>
+#include <osmocom/msc/iucs.h>
+#include <osmocom/msc/vlr.h>
+
+#include "../../bscconfig.h"
+
+#define S(x) (1 << (x))
+
+#define CONN_ID_RAN 1
+#define CONN_ID_CN 2
+
+#define MGCP_MGW_TIMEOUT 4 /* in seconds */
+#define MGCP_MGW_TIMEOUT_TIMER_NR 1
+#define MGCP_RAN_TIMEOUT 10 /* in seconds */
+#define MGCP_RAN_TIMEOUT_TIMER_NR 2
+#define MGCP_REL_TIMEOUT 60 /* in seconds */
+#define MGCP_REL_TIMEOUT_TIMER_NR 3
+#define MGCP_ASS_TIMEOUT 10 /* in seconds */
+#define MGCP_ASS_TIMEOUT_TIMER_NR 4
+
+#define MGCP_ENDPOINT_FORMAT "%x@mgw"
+
+/* Some internal cause codes to indicate fault condition inside the FSM */
+enum msc_mgcp_cause_code {
+ MGCP_ERR_MGW_FAIL,
+ MGCP_ERR_MGW_INVAL_RESP,
+ MGCP_ERR_MGW_TX_FAIL,
+ MGCP_ERR_UNEXP_TEARDOWN,
+ MGCP_ERR_UNSUPP_ADDR_FMT,
+ MGCP_ERR_RAN_TIMEOUT,
+ MGCP_ERR_ASS_TIMEOUT,
+ MGCP_ERR_NOMEM,
+ MGCP_ERR_ASSGMNT_FAIL
+};
+
+/* Human readable respresentation of the faul codes, will be displayed by
+ * handle_error() */
+static const struct value_string msc_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"},
+ {MGCP_ERR_UNSUPP_ADDR_FMT, "unsupported network address format used (RAN)"},
+ {MGCP_ERR_RAN_TIMEOUT, "call could not be completed in time (RAN)"},
+ {MGCP_ERR_ASS_TIMEOUT, "assignment could not be completed in time (RAN)"},
+ {MGCP_ERR_NOMEM, "out of memory"},
+ {MGCP_ERR_ASSGMNT_FAIL, "assignment failure (RAN)"},
+ {0, NULL}
+};
+
+enum fsm_msc_mgcp_states {
+ ST_CRCX_RAN,
+ ST_CRCX_CN,
+ ST_CRCX_COMPL,
+ ST_MDCX_CN,
+ ST_MDCX_CN_COMPL,
+ ST_MDCX_RAN,
+ ST_MDCX_RAN_COMPL,
+ ST_CALL,
+ ST_HALT,
+};
+
+enum msc_mgcp_fsm_evt {
+ /* Initial event: start off the state machine */
+ EV_INIT,
+
+ /* External event: Notify that the Assignment is complete and we
+ * may now forward IP/Port of the remote call leg to the MGW */
+ EV_ASSIGN,
+
+ /* External event: Notify that the Call is complete and that the
+ * two half open connections on the MGW should now be connected */
+ EV_CONNECT,
+
+ /* External event: Notify that the call is over and the connections
+ * on the mgw shall be removed */
+ EV_TEARDOWN,
+
+ /* Internal event: An error occurred that requires a controlled
+ * teardown of the RTP connections */
+ EV_TEARDOWN_ERROR,
+
+ /* Internal event: The mgcp_gw has sent its CRCX response for
+ * the RAN side */
+ EV_CRCX_RAN_RESP,
+
+ /* Internal event: The mgcp_gw has sent its CRCX response for
+ * the CN side */
+ EV_CRCX_CN_RESP,
+
+ /* Internal event: The mgcp_gw has sent its MDCX response for
+ * the RAN side */
+ EV_MDCX_RAN_RESP,
+
+ /* Internal event: The mgcp_gw has sent its MDCX response for
+ * the CN side */
+ EV_MDCX_CN_RESP,
+
+ /* Internal event: The mgcp_gw has sent its DLCX response for
+ * the RAN and CN side */
+ EV_DLCX_ALL_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 msc_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(msc_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_ERROR, mgcp_ctx);
+}
+
+/* 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);
+
+ if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
+ /* 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_ERROR, mgcp_ctx);
+ } else if (fi->T == MGCP_RAN_TIMEOUT_TIMER_NR) {
+ /* If the logic that controls the RAN 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_RAN_TIMEOUT);
+ } else if (fi->T == MGCP_REL_TIMEOUT_TIMER_NR) {
+ /* Under normal conditions, the MSC logic should always command
+ * to release the call at some point. However, the release may
+ * be missing due to errors in the MSC logic and we may have
+ * reached ST_HALT because of cascading errors and timeouts. In
+ * this and only in this case we will allow ST_HALT to free all
+ * context information on its own authority. */
+ mgcp_ctx->free_ctx = true;
+
+ /* 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_ASS_TIMEOUT_TIMER_NR) {
+ /* There may be rare cases in which the MSC is unable to
+ * complete the call assignment */
+ handle_error(mgcp_ctx, MGCP_ERR_ASS_TIMEOUT);
+ } else {
+ /* Ther must not be any unsolicited timers in this FSM. If so,
+ * we have serious problem. */
+ OSMO_ASSERT(false);
+ }
+
+ return 0;
+}
+
+static void mgw_crcx_ran_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CRCX_RAN: Send CRCX for RAN side to MGW */
+static void fsm_crcx_ran_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ struct mgcp_client *mgcp;
+ struct mgcp_msg mgcp_msg;
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ mgcp_ctx->rtp_endpoint = mgcp_client_next_endpoint(mgcp);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "CRCX/RAN: creating connection for the RAN side on MGW endpoint:0x%x...\n", mgcp_ctx->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 = mgcp_ctx->rtp_endpoint,
+ .conn_mode = MGCP_CONN_LOOPBACK
+ };
+ if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, mgcp_ctx->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, mgw_crcx_ran_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_CN, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for RAN associated CRCX */
+static void mgw_crcx_ran_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+ int rc;
+ struct gsm_trans *trans;
+ struct gsm_subscriber_connection *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+ conn = trans->conn;
+ OSMO_ASSERT(conn);
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "CRCX/RAN: 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_ran, r->head.conn_id, sizeof(mgcp_ctx->conn_id_ran));
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/RAN: MGW responded with CI: %s\n", mgcp_ctx->conn_id_ran);
+
+ rc = mgcp_response_parse_params(r);
+ if (rc) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/RAN: Cannot parse response\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);
+
+ conn->rtp.local_port_ran = r->audio_port;
+ osmo_strlcpy(conn->rtp.local_addr_ran, r->audio_ip, sizeof(conn->rtp.local_addr_ran));
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_RAN_RESP, mgcp_ctx);
+}
+
+static void mgw_crcx_cn_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CRCX_CN: check MGW response and send CRCX for CN side to MGW */
+static void fsm_crcx_cn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ struct mgcp_client *mgcp;
+ struct mgcp_msg mgcp_msg;
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ switch (event) {
+ case EV_CRCX_RAN_RESP:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "CRCX/CN creating connection for the CN side on MGW endpoint:0x%x...\n", mgcp_ctx->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 = mgcp_ctx->rtp_endpoint,
+ .conn_mode = MGCP_CONN_LOOPBACK
+ };
+ if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, mgcp_ctx->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, mgw_crcx_cn_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_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for CN associated CRCX */
+static void mgw_crcx_cn_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+ int rc;
+ struct gsm_trans *trans;
+ struct gsm_subscriber_connection *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+ conn = trans->conn;
+ OSMO_ASSERT(conn);
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "CRCX/CN: 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_cn, r->head.conn_id, sizeof(mgcp_ctx->conn_id_cn));
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/CN: MGW responded with CI: %s\n", mgcp_ctx->conn_id_cn);
+
+ rc = mgcp_response_parse_params(r);
+ if (rc) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/CN: Cannot parse response\n");
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/CN: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+ conn->rtp.local_port_cn = r->audio_port;
+ osmo_strlcpy(conn->rtp.local_addr_cn, r->audio_ip, sizeof(conn->rtp.local_addr_cn));
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_CN_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_CRCX_COMPL: check MGW response, start assignment */
+static void fsm_crcx_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ struct gsm_trans *trans;
+ struct gsm_subscriber_connection *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+ conn = trans->conn;
+ OSMO_ASSERT(conn);
+
+ switch (event) {
+ case EV_CRCX_CN_RESP:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ /* Forward assignment request to A/RANAP */
+ if (conn->via_ran == RAN_UTRAN_IU) {
+#ifdef BUILD_IU
+ /* Assign a voice channel via RANAP on 3G */
+ if (iu_rab_act_cs(trans))
+ goto error;
+#else
+ LOGPFSML(fi, LOGL_ERROR, "Cannot send Iu RAB Assignment: built without Iu support\n");
+ goto error;
+#endif
+ } else if (conn->via_ran == RAN_GERAN_A) {
+ /* Assign a voice channel via A on 2G */
+ if (a_iface_tx_assignment(trans))
+ goto error;
+ } else {
+ /* Unset or unimplemented new RAN type */
+ LOGPFSML(fi, LOGL_ERROR, "Unknown RAN type: %d\n", conn->via_ran);
+ return;
+ }
+
+ /* Respond back to MNCC (if requested) */
+ if (trans->tch_rtp_create) {
+ if (gsm48_tch_rtp_create(trans))
+ goto error;
+ }
+
+ /* Note: When we reach this point then the situation is basically that
+ * we have two sides connected, both are in loopback. The local ports
+ * of the side pointing towards the BSS should be already communicated
+ * and we are waiting now for the BSS to return with the assignment
+ * complete command. */
+ osmo_fsm_inst_state_chg(fi, ST_MDCX_CN, MGCP_RAN_TIMEOUT, MGCP_RAN_TIMEOUT_TIMER_NR);
+ return;
+
+error:
+ handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
+}
+
+static void mgw_mdcx_cn_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_MDCX_CN: send MDCX for RAN side to MGW */
+static void fsm_mdcx_cn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ struct mgcp_client *mgcp;
+ struct gsm_trans *trans;
+ struct gsm_subscriber_connection *conn;
+ struct mgcp_msg mgcp_msg;
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+ conn = trans->conn;
+ OSMO_ASSERT(conn);
+
+ switch (event) {
+ case EV_CONNECT:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "MDCX/CN: completing connection for the CN side on MGW endpoint:0x%x, remote leg expects RTP input on address %s:%u\n",
+ mgcp_ctx->rtp_endpoint, conn->rtp.remote_addr_cn, conn->rtp.remote_port_cn);
+
+ /* 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 = mgcp_ctx->rtp_endpoint,
+ .conn_id = mgcp_ctx->conn_id_cn,
+ .conn_mode = MGCP_CONN_RECV_SEND,
+ .audio_ip = conn->rtp.remote_addr_cn,
+ .audio_port = conn->rtp.remote_port_cn
+ };
+ if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, mgcp_ctx->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, mgw_mdcx_cn_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, ST_MDCX_CN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for CN associated CRCX */
+static void mgw_mdcx_cn_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "MDCX/CN: 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_CN_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_MDCX_CN_COMPL: wait for mgw response, move on with the MDCX
+ * for the RAN side if we already have valid IP/Port data for the RAN sided
+ * RTP stream. */
+static void fsm_mdcx_cn_compl_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ struct gsm_subscriber_connection *conn;
+ struct gsm_trans *trans;
+
+ OSMO_ASSERT(mgcp_ctx);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+ conn = trans->conn;
+ OSMO_ASSERT(conn);
+
+ switch (event) {
+ case EV_MDCX_CN_RESP:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ /* Enter MDCX phase, but we must be sure that the Assigmnet on the A or
+ * IuCS interface is complete (IP-Address and Port are valid) */
+ osmo_fsm_inst_state_chg(fi, ST_MDCX_RAN, MGCP_ASS_TIMEOUT, MGCP_ASS_TIMEOUT_TIMER_NR);
+
+ /* If we already have a valid remote port and IP-Address from the RAN side
+ * call leg, the assignment has been completed before we got here, so we
+ * may move on immediately */
+ if (conn->rtp.remote_port_ran != 0 || strlen(conn->rtp.remote_addr_ran) > 0)
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASSIGN, mgcp_ctx);
+}
+
+static void mgw_mdcx_ran_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_MDCX_RAN: wait for assignment completion, send MDCX for CN side to MGW */
+static void fsm_mdcx_ran_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ struct mgcp_client *mgcp;
+ struct gsm_trans *trans;
+ struct gsm_subscriber_connection *conn;
+ struct mgcp_msg mgcp_msg;
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+ conn = trans->conn;
+ OSMO_ASSERT(conn);
+
+ switch (event) {
+ case EV_ASSIGN:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "MDCX/RAN: completing connection for the CN side on MGW endpoint:0x%x, RAN expects RTP input on address %s:%u\n",
+ mgcp_ctx->rtp_endpoint, conn->rtp.remote_addr_ran, conn->rtp.remote_port_ran);
+
+ /* 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 = mgcp_ctx->rtp_endpoint,
+ .conn_id = mgcp_ctx->conn_id_ran,
+ .conn_mode = MGCP_CONN_RECV_SEND,
+ .audio_ip = conn->rtp.remote_addr_ran,
+ .audio_port = conn->rtp.remote_port_ran
+ };
+ if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, mgcp_ctx->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, mgw_mdcx_ran_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, ST_MDCX_RAN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for CN associated CRCX */
+static void mgw_mdcx_ran_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "MDCX/RAN: 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_RAN_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_MDCX_RAN_COMPL: check MGW response */
+static void fsm_mdcx_ran_compl_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_MDCX_RAN_RESP:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ LOGPFSML(fi, LOGL_DEBUG, "call active, waiting for teardown...\n");
+ osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
+}
+
+static void mgw_dlcx_all_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CALL: call is active, send DLCX for both sides on teardown */
+static void fsm_call_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct mgcp_client *mgcp;
+ struct mgcp_msg mgcp_msg;
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "DLCX: removing connection for the RAN and CN side on MGW endpoint:0x%x...\n", mgcp_ctx->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(mgcp_ctx->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 = mgcp_ctx->rtp_endpoint
+ };
+ if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, mgcp_ctx->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, mgw_dlcx_all_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, ST_HALT, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for CN associated CRCX */
+static void mgw_dlcx_all_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ 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);
+ 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_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 = data;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ /* NOTE: We must not free the context information now, we have to
+ * wait until msc_mgcp_call_release() is called. Then we are sure
+ * that the logic controlling us is fully aware that the context
+ * information is freed. If we would free early now the controlling
+ * logic might mistakenly think that the context info is still alive,
+ * so lets keep the context info until we are explicitly asked for
+ * throwing it away. */
+ if (mgcp_ctx->free_ctx) {
+ osmo_fsm_inst_free(mgcp_ctx->fsm);
+ talloc_free(mgcp_ctx);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, ST_HALT, MGCP_REL_TIMEOUT, MGCP_REL_TIMEOUT_TIMER_NR);
+}
+
+static struct osmo_fsm_state fsm_msc_mgcp_states[] = {
+
+ /* Startup state machine, send CRCX for RAN side. */
+ [ST_CRCX_RAN] = {
+ .in_event_mask = S(EV_INIT),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_CN),
+ .name = OSMO_STRINGIFY(ST_CRCX_RAN),
+ .action = fsm_crcx_ran_cb,
+ },
+ /* When the response to the RAN CRCX is received, then proceed with
+ sending the CRCX for CN side */
+ [ST_CRCX_CN] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_CRCX_RAN_RESP),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_CRCX_COMPL),
+ .name = OSMO_STRINGIFY(ST_CRCX_CN),
+ .action = fsm_crcx_cn_cb,
+ },
+ /* Complete the CRCX phase by starting the assignment. Depending on the
+ * RAT (Radio Access Technology), this will either trigger an
+ * Assignment Request on the A-Interface or an RAB-Assignment on the
+ * IU-interface */
+ [ST_CRCX_COMPL] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_CRCX_CN_RESP),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_CN),
+ .name = OSMO_STRINGIFY(ST_CRCX_COMPL),
+ .action = fsm_crcx_compl,
+ },
+ /* Wait for MSC to complete the assignment request, when complete, we
+ * will enter the MDCX phase by sending an MDCX for the CN side to the
+ * MGW */
+ [ST_MDCX_CN] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_CONNECT),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_CN_COMPL),
+ .name = OSMO_STRINGIFY(ST_MDCX_CN),
+ .action = fsm_mdcx_cn_cb,
+ },
+ /* We arrive in this state when the MDCX phase for the CN side has
+ * completed we will check the IP/Port of the RAN connection. If this
+ * data is valid we may continue with the MDCX phase for the RAN side.
+ * If not we wait until the assinment completes on the A or on the IuCS
+ * interface. The completion of the assignment will fill in the port and
+ * IP-Address of the RAN side and way may continue then. */
+ [ST_MDCX_CN_COMPL] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_MDCX_CN_RESP),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_RAN),
+ .name = OSMO_STRINGIFY(ST_MDCX_CN_COMPL),
+ .action = fsm_mdcx_cn_compl_cb,
+ },
+ /* When the response for the CN MDCX is received, send the MDCX for the
+ * RAN side to the MGW */
+ [ST_MDCX_RAN] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_ASSIGN),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_RAN_COMPL),
+ .name = OSMO_STRINGIFY(ST_MDCX_RAN),
+ .action = fsm_mdcx_ran_cb,
+ },
+ /* The RAN side MDCX phase is complete when the response is received
+ * from the MGW. The call is then active, we change to ST_CALL and wait
+ * there until the call ends. */
+ [ST_MDCX_RAN_COMPL] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_MDCX_RAN_RESP),
+ .out_state_mask = S(ST_HALT) | S(ST_CALL),
+ .name = OSMO_STRINGIFY(ST_MDCX_RAN_COMPL),
+ .action = fsm_mdcx_ran_compl_cb,
+ },
+ /* We are now in the active call phase, wait until the call is done
+ * and send a DLCX then to remove all connections from the MGW */
+ [ST_CALL] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR),
+ .out_state_mask = S(ST_HALT),
+ .name = OSMO_STRINGIFY(ST_CALL),
+ .action = fsm_call_cb,
+ },
+ /* When the MGW confirms that the connections are terminated, then halt
+ * the state machine. */
+ [ST_HALT] = {
+ .in_event_mask = S(EV_TEARDOWN) | S(EV_TEARDOWN_ERROR) | S(EV_DLCX_ALL_RESP),
+ .out_state_mask = S(ST_HALT),
+ .name = OSMO_STRINGIFY(ST_HALT),
+ .action = fsm_halt_cb,
+ },
+};
+
+/* State machine definition */
+static struct osmo_fsm fsm_msc_mgcp = {
+ .name = "MGW",
+ .states = fsm_msc_mgcp_states,
+ .num_states = ARRAY_SIZE(fsm_msc_mgcp_states),
+ .log_subsys = DMGCP,
+ .timer_cb = fsm_timeout_cb,
+};
+
+/* Notify that a new call begins. This will create a connection for the
+ * RAN and the CN on the MGW.
+ * Parameter:
+ * trans: transaction context.
+ * Returns -EINVAL on error, 0 on success. */
+int msc_mgcp_call_assignment(struct gsm_trans *trans)
+{
+ struct mgcp_ctx *mgcp_ctx;
+ char name[32];
+ static bool fsm_registered = false;
+ struct gsm_subscriber_connection *conn;
+ struct mgcp_client *mgcp;
+
+ OSMO_ASSERT(trans);
+
+ if (!trans->conn) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid conn, call assignment failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+
+ conn = trans->conn;
+ mgcp = conn->network->mgw.client;
+ OSMO_ASSERT(mgcp);
+
+ if (conn->rtp.mgcp_ctx) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) double assignment detected, dropping...\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+
+#ifdef BUILD_IU
+ /* FIXME: HACK. where to scope the RAB Id? At the conn / subscriber / ranap_ue_conn_ctx? */
+ static uint8_t next_iu_rab_id = 1;
+ if (conn->via_ran == RAN_UTRAN_IU)
+ conn->iu.rab_id = next_iu_rab_id++;
+#endif
+
+ if (snprintf(name, sizeof(name), "MGW_%i", trans->transaction_id) >= sizeof(name))
+ return -EINVAL;
+
+ /* Register the fsm description (if not already done) */
+ if (fsm_registered == false) {
+ osmo_fsm_register(&fsm_msc_mgcp);
+ fsm_registered = true;
+ }
+
+ /* Allocate and configure a new fsm instance */
+ mgcp_ctx = talloc_zero(NULL, struct mgcp_ctx);
+ OSMO_ASSERT(mgcp_ctx);
+
+ mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm_msc_mgcp, NULL, NULL, LOGL_DEBUG, name);
+ OSMO_ASSERT(mgcp_ctx->fsm);
+ mgcp_ctx->fsm->priv = mgcp_ctx;
+ mgcp_ctx->mgcp = mgcp;
+ mgcp_ctx->trans = trans;
+
+ /* start state machine */
+ OSMO_ASSERT(mgcp_ctx->fsm->state == ST_CRCX_RAN);
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_INIT, mgcp_ctx);
+
+ conn->rtp.mgcp_ctx = mgcp_ctx;
+
+ LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) call assignment initiated\n",
+ vlr_subscr_name(conn->vsub));
+
+ return 0;
+}
+
+/* Inform the FSM that the assignment (RAN connection) is now complete.
+ * Parameter:
+ * conn: subscriber connection context.
+ * port: port number of the remote leg.
+ * addr: IP-address of the remote leg.
+ * Returns -EINVAL on error, 0 on success. */
+int msc_mgcp_ass_complete(struct gsm_subscriber_connection *conn, uint16_t port, char *addr)
+{
+ struct mgcp_ctx *mgcp_ctx;
+
+ OSMO_ASSERT(conn);
+
+ if (port == 0) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid remote call leg port, assignmnet completion failed\n",
+ vlr_subscr_name(conn->vsub));
+ return -EINVAL;
+ }
+ if (!addr || strlen(addr) <= 0) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) missing remote call leg address, assignmnet completion failed\n",
+ vlr_subscr_name(conn->vsub));
+ return -EINVAL;
+ }
+
+ mgcp_ctx = conn->rtp.mgcp_ctx;
+ if (!mgcp_ctx) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid mgcp context, assignmnet completion failed.\n",
+ vlr_subscr_name(conn->vsub));
+ return -EINVAL;
+ }
+
+ /* Memorize port and IP-Address of the remote RAN call leg. We need this
+ * information at latest when we enter the MDCX phase for the RAN side. */
+ conn->rtp.remote_port_ran = port;
+ osmo_strlcpy(conn->rtp.remote_addr_ran, addr, sizeof(conn->rtp.remote_addr_ran));
+
+ LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) assignmnet completed, rtp %s:%d.\n",
+ vlr_subscr_name(conn->vsub), conn->rtp.remote_addr_ran, port);
+
+ /* Note: We only dispatch the event if we are really waiting for the
+ * assignment, if we are not yet waiting, there is no need to loudly
+ * broadcast an event that the all other states do not understand anyway */
+ if (mgcp_ctx->fsm->state == ST_MDCX_RAN)
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASSIGN, mgcp_ctx);
+
+ return 0;
+}
+
+/* Make the connection of a previously assigned call complete
+ * Parameter:
+ * trans: transaction context.
+ * port: port number of the remote leg.
+ * addr: IP-address of the remote leg.
+ * Returns -EINVAL on error, 0 on success. */
+int msc_mgcp_call_complete(struct gsm_trans *trans, uint16_t port, char *addr)
+{
+ struct mgcp_ctx *mgcp_ctx;
+ struct gsm_subscriber_connection *conn;
+
+ OSMO_ASSERT(trans);
+ OSMO_ASSERT(addr);
+
+ if (port == 0) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid remote call leg port, call completion failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+ if (!addr || strlen(addr) <= 0) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) missing remote call leg address, call completion failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+ if (!trans->conn) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid conn, call completion failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid mgcp context, call completion failed.\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx->fsm) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) no FSM, call completion failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+
+ mgcp_ctx = trans->conn->rtp.mgcp_ctx;
+
+ /* The FSM should already have passed all CRCX phases and be ready to move
+ * on with the MDCX phases. */
+ if (mgcp_ctx->fsm->state != ST_MDCX_CN) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid call state, call completion failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+
+ conn = trans->conn;
+ osmo_strlcpy(conn->rtp.remote_addr_cn, addr, sizeof(conn->rtp.remote_addr_cn));
+ conn->rtp.remote_port_cn = port;
+
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CONNECT, mgcp_ctx);
+
+ LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) call completion initiated\n",
+ vlr_subscr_name(conn->vsub));
+
+ return 0;
+}
+
+/* Release ongoing call.
+ * Parameter:
+ * trans: connection context.
+ * Returns -EINVAL on error, 0 on success. */
+int msc_mgcp_call_release(struct gsm_trans *trans)
+{
+ struct mgcp_ctx *mgcp_ctx;
+
+ OSMO_ASSERT(trans);
+
+ if (!trans->conn) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid conn, call release failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) invalid mgcp context, call release failed.\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx->fsm) {
+ LOGP(DMGCP, LOGL_ERROR, "(subscriber:%s) no FSM, call release failed\n",
+ vlr_subscr_name(trans->vsub));
+ return -EINVAL;
+ }
+
+ mgcp_ctx = trans->conn->rtp.mgcp_ctx;
+
+ /* Inform the FSM that as soon as it reaches ST_HALT it may free
+ * all context information immediately */
+ mgcp_ctx->free_ctx = true;
+
+ /* Initaite teardown, regardless of which state we are currently
+ * in */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+
+ /* Prevent any further operation that is triggered from outside by
+ * overwriting the context pointer with NULL. The FSM will now
+ * take care for a graceful shutdown and when done it will free
+ * all related context information */
+ trans->conn->rtp.mgcp_ctx = NULL;
+
+ LOGP(DMGCP, LOGL_DEBUG, "(subscriber:%s) call release initiated\n",
+ vlr_subscr_name(trans->vsub));
+
+ return 0;
+}