diff options
author | Philipp Maier <pmaier@sysmocom.de> | 2017-11-07 17:19:25 +0100 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2018-02-05 22:28:43 +0000 |
commit | 621ba032bdf60383f5431ca936040c620551a3d5 (patch) | |
tree | 0fd222b08503b64e6d4109a7f3c0b678b16bf1ec /src/libmsc/msc_mgcp.c | |
parent | 64dbc5464c7ef8f0ba2acbb1c924d4eef3e6e72e (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.c | 1078 |
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; +} |