This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
dexter gerrit-no-reply at lists.osmocom.org
Review at https://gerrit.osmocom.org/4980
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
Closes: OS#2605
Change-Id: Ieea9630358b3963261fa1993cf1f3b563ff23538
---
M include/osmocom/msc/Makefile.am
M include/osmocom/msc/gsm_data.h
M include/osmocom/msc/msc_ifaces.h
A include/osmocom/msc/msc_mgcp.h
M src/libmsc/Makefile.am
M src/libmsc/a_iface.c
M src/libmsc/a_iface_bssap.c
M src/libmsc/gsm_04_08.c
M src/libmsc/msc_ifaces.c
A src/libmsc/msc_mgcp.c
10 files changed, 1,122 insertions(+), 219 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-msc refs/changes/80/4980/1
diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am
index 1419e8e..052d830 100644
--- a/include/osmocom/msc/Makefile.am
+++ b/include/osmocom/msc/Makefile.am
@@ -39,6 +39,7 @@
mncc.h \
mncc_int.h \
msc_ifaces.h \
+ msc_mgcp.h \
network_listen.h \
oap_client.h \
openbscdefines.h \
diff --git a/include/osmocom/msc/gsm_data.h b/include/osmocom/msc/gsm_data.h
index 4d493cb..5eecc15 100644
--- a/include/osmocom/msc/gsm_data.h
+++ b/include/osmocom/msc/gsm_data.h
@@ -195,9 +195,17 @@
struct gsm_encr encr;
struct {
+ struct mgcp_ctx *mgcp_ctx;
unsigned int mgcp_rtp_endpoint;
- uint16_t port_subscr;
- uint16_t port_cn;
+
+ uint16_t local_port_ran;
+ char local_addr_ran[INET_ADDRSTRLEN];
+ uint16_t remote_port_ran;
+ char remote_addr_ran[INET_ADDRSTRLEN];
+ uint16_t local_port_cn;
+ char local_addr_cn[INET_ADDRSTRLEN];
+ uint16_t remote_port_cn;
+ char remote_addr_cn[INET_ADDRSTRLEN];
} rtp;
/* which Iu-CS connection, if any. */
diff --git a/include/osmocom/msc/msc_ifaces.h b/include/osmocom/msc/msc_ifaces.h
index 0592c07..5e560b3 100644
--- a/include/osmocom/msc/msc_ifaces.h
+++ b/include/osmocom/msc/msc_ifaces.h
@@ -41,3 +41,4 @@
int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2);
void msc_call_release(struct gsm_trans *trans);
int msc_call_connect(struct gsm_trans *trans, uint16_t port, uint32_t ip);
+int msc_iu_rab_act_cs(struct gsm_trans *trans);
diff --git a/include/osmocom/msc/msc_mgcp.h b/include/osmocom/msc/msc_mgcp.h
new file mode 100644
index 0000000..7fb5411
--- /dev/null
+++ b/include/osmocom/msc/msc_mgcp.h
@@ -0,0 +1,43 @@
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info at 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/>.
+ *
+ */
+
+#pragma once
+
+/* MGCP state handler context (fsm etc..) */
+struct mgcp_ctx {
+ /* FSM instance, which handles the connection switching procedure */
+ struct osmo_fsm_inst *fsm;
+
+ /* RTP endpoint number */
+ uint16_t rtp_endpoint;
+
+ /* RTP connection identifiers */
+ char conn_id_ran[MGCP_CONN_ID_MAXLEN];
+ char conn_id_cn[MGCP_CONN_ID_MAXLEN];
+
+ /* Copy of the pointer and the data with context information
+ * needed to process the AoIP and MGCP requests (system data) */
+ struct mgcp_client *mgcp;
+ struct gsm_trans *trans;
+};
+
+int msc_mgcp_call_assignment(struct gsm_trans *trans);
+int msc_mgcp_call_complete(struct gsm_trans *trans, uint16_t port, char *addr);
+int msc_mgcp_call_release(struct gsm_trans *trans);
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index fee9f44..e872d03 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -40,6 +40,7 @@
mncc_builtin.c \
mncc_sock.c \
msc_ifaces.c \
+ msc_mgcp.c \
rrlp.c \
silent_call.c \
sms_queue.c \
diff --git a/src/libmsc/a_iface.c b/src/libmsc/a_iface.c
index 4892fb8..927efda 100644
--- a/src/libmsc/a_iface.c
+++ b/src/libmsc/a_iface.c
@@ -408,8 +408,8 @@
/* Package RTP-Address data */
memset(&rtp_addr_in, 0, sizeof(rtp_addr_in));
rtp_addr_in.sin_family = AF_INET;
- rtp_addr_in.sin_port = osmo_htons(conn->rtp.port_subscr);
- rtp_addr_in.sin_addr.s_addr = osmo_htonl(mgcp_client_remote_addr_n(gsm_network->mgw.client));
+ rtp_addr_in.sin_port = osmo_htons(conn->rtp.local_port_ran);
+ rtp_addr_in.sin_addr.s_addr = inet_addr(conn->rtp.local_addr_ran);
memset(&rtp_addr, 0, sizeof(rtp_addr));
memcpy(&rtp_addr, &rtp_addr_in, sizeof(rtp_addr_in));
diff --git a/src/libmsc/a_iface_bssap.c b/src/libmsc/a_iface_bssap.c
index 922dca9..564f1b9 100644
--- a/src/libmsc/a_iface_bssap.c
+++ b/src/libmsc/a_iface_bssap.c
@@ -596,11 +596,9 @@
* transport address element */
if (rtp_addr.ss_family == AF_INET) {
rtp_addr_in = (struct sockaddr_in *)&rtp_addr;
- conn->rtp.port_subscr = osmo_ntohs(rtp_addr_in->sin_port);
- /* FIXME: We also get the IP-Address of the remote (e.g. BTS)
- * end with the response. Currently we just ignore that address.
- * Instead we expect that our local MGCP gateway and the code
- * controlling it, magically knows the IP of the remote end. */
+ conn->rtp.remote_port_ran = osmo_ntohs(rtp_addr_in->sin_port);
+ strncpy(conn->rtp.remote_addr_ran, inet_ntoa(rtp_addr_in->sin_addr),
+ sizeof(conn->rtp.remote_addr_ran));
} else {
LOGP(DMSC, LOGL_ERROR, "Unsopported addressing scheme. (supports only IPV4)\n");
goto fail;
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index 3f685d5..8f317d6 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -2667,8 +2667,8 @@
* (0 if unknown) */
msg_type = GSM_TCHF_FRAME;
- uint32_t addr = mgcp_client_remote_addr_n(net->mgw.client);
- uint16_t port = trans->conn->rtp.port_cn;
+ uint32_t addr = inet_addr(trans->conn->rtp.local_addr_cn);
+ uint16_t port = trans->conn->rtp.local_port_cn;
/* FIXME: This has to be set to some meaningful value,
* before the MSC-Split, this value was pulled from
diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c
index e29fe0e..bde7510 100644
--- a/src/libmsc/msc_ifaces.c
+++ b/src/libmsc/msc_ifaces.c
@@ -29,6 +29,8 @@
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/a_iface.h>
#include <osmocom/msc/gsm_04_08.h>
+#include <osmocom/msc/msc_mgcp.h>
+
#include "../../bscconfig.h"
@@ -142,13 +144,24 @@
return ranap_iu_tx_common_id(conn->iu.ue_ctx, conn->vsub->imsi);
}
-static int iu_rab_act_cs(struct ranap_ue_conn_ctx *uectx, uint8_t rab_id,
- uint32_t rtp_ip, uint16_t rtp_port)
+int msc_iu_rab_act_cs(struct gsm_trans *trans)
{
#ifdef BUILD_IU
+ struct gsm_subscriber_connection *conn
struct msgb *msg;
bool use_x213_nsap;
- uint32_t conn_id = uectx->conn_id;
+ uint32_t conn_id;
+ struct ranap_ue_conn_ctx *uectx;
+ uint8_t rab_id;
+ uint32_t rtp_ip;
+ uint16_t rtp_port;
+
+ conn = trans->conn;
+ uectx = conn->iu.ue_ctx;
+ rab_id = conn->iu.rab_id;
+ rtp_ip = inet_addr(conn->rtp.local_addr_ran);
+ rtp_port = conn->rtp.local_port_ran;
+ conn_id = uectx->conn_id;
use_x213_nsap = (uectx->rab_assign_addr_enc == RANAP_NSAP_ADDR_ENC_X213);
@@ -171,172 +184,21 @@
#endif
}
-static void mgcp_response_rab_act_cs_crcx(struct mgcp_response *r, void *priv)
-{
- struct gsm_trans *trans = priv;
- struct gsm_subscriber_connection *conn = trans->conn;
- uint32_t rtp_ip;
- int rc;
-
- if (r->head.response_code != 200) {
- LOGP(DMGCP, LOGL_ERROR,
- "MGCPGW response yields error: %d %s\n",
- r->head.response_code, r->head.comment);
- goto rab_act_cs_error;
- }
-
- rc = mgcp_response_parse_params(r);
- if (rc) {
- LOGP(DMGCP, LOGL_ERROR,
- "Cannot parse MGCP response, for %s\n",
- vlr_subscr_name(trans->vsub));
- goto rab_act_cs_error;
- }
-
- conn->rtp.port_cn = r->audio_port;
-
- rtp_ip = mgcp_client_remote_addr_n(conn->network->mgw.client);
-
- if (trans->conn->via_ran == RAN_UTRAN_IU) {
- /* Assign a voice channel via RANAP on 3G */
- if (iu_rab_act_cs(conn->iu.ue_ctx, conn->iu.rab_id, rtp_ip, conn->rtp.port_subscr))
- goto rab_act_cs_error;
- } else if (trans->conn->via_ran == RAN_GERAN_A) {
- /* Assign a voice channel via A on 2G */
- if (a_iface_tx_assignment(trans))
- goto rab_act_cs_error;
- } else
- goto rab_act_cs_error;
-
- /* Respond back to MNCC (if requested) */
- if (trans->tch_rtp_create) {
- if (gsm48_tch_rtp_create(trans))
- goto rab_act_cs_error;
- }
- return;
-
-rab_act_cs_error:
- /* FIXME abort call, invalidate conn, ... */
- LOGP(DMSC, LOGL_ERROR, "%s: failure during assignment\n",
- vlr_subscr_name(trans->vsub));
- return;
-}
-
int msc_call_assignment(struct gsm_trans *trans)
{
- struct gsm_subscriber_connection *conn;
- struct mgcp_client *mgcp;
- struct msgb *msg;
- uint16_t bts_base;
-
if (!trans)
return -EINVAL;
if (!trans->conn)
return -EINVAL;
- conn = trans->conn;
- mgcp = conn->network->mgw.client;
-
-#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
-
- conn->rtp.mgcp_rtp_endpoint =
- mgcp_client_next_endpoint(conn->network->mgw.client);
-
- /* This will calculate the port we assign to the BTS via AoIP
- * assignment command (or rab-assignment on 3G) The BTS will send
- * its RTP traffic to that port on the MGCPGW side. The MGCPGW only
- * gets the endpoint ID via the CRCX. It will do the same calculation
- * on his side too to get knowledge of the rtp port. */
- bts_base = mgcp_client_conf_actual(mgcp)->bts_base;
- conn->rtp.port_subscr = bts_base + 2 * conn->rtp.mgcp_rtp_endpoint;
-
- /* Establish the RTP stream first as looping back to the originator.
- * The MDCX will patch through to the counterpart. TODO: play a ring
- * tone instead. */
- msg = mgcp_msg_crcx(mgcp, conn->rtp.mgcp_rtp_endpoint,
- conn->rtp.mgcp_rtp_endpoint, MGCP_CONN_LOOPBACK);
- return mgcp_client_tx(mgcp, msg, mgcp_response_rab_act_cs_crcx, trans);
-}
-
-static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv);
-
-static void mgcp_bridge(struct gsm_trans *from, struct gsm_trans *to,
- enum bridge_state state,
- enum mgcp_connection_mode mode)
-{
- struct gsm_subscriber_connection *conn1 = from->conn;
- struct gsm_subscriber_connection *conn2 = to->conn;
- struct mgcp_client *mgcp = conn1->network->mgw.client;
- const char *ip;
- struct msgb *msg;
-
- OSMO_ASSERT(mgcp);
-
- from->bridge.peer = to;
- from->bridge.state = state;
-
- /* Loop back to the same MGCP GW */
- ip = mgcp_client_remote_addr_str(mgcp);
-
- msg = mgcp_msg_mdcx(mgcp,
- conn1->rtp.mgcp_rtp_endpoint,
- ip, conn2->rtp.port_cn,
- mode);
- if (mgcp_client_tx(mgcp, msg, mgcp_response_bridge_mdcx, from))
- LOGP(DMGCP, LOGL_ERROR,
- "Failed to send MDCX message for %s\n",
- vlr_subscr_name(from->vsub));
-}
-
-static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv)
-{
- struct gsm_trans *trans = priv;
- struct gsm_trans *peer = trans->bridge.peer;
-
- switch (trans->bridge.state) {
- case BRIDGE_STATE_LOOPBACK_PENDING:
- trans->bridge.state = BRIDGE_STATE_LOOPBACK_ESTABLISHED;
-
- switch (peer->bridge.state) {
- case BRIDGE_STATE_LOOPBACK_PENDING:
- /* Wait until the other is done as well. */
- return;
- case BRIDGE_STATE_LOOPBACK_ESTABLISHED:
- /* Now that both are in loopback, switch both to
- * forwarding. */
- mgcp_bridge(trans, peer, BRIDGE_STATE_BRIDGE_PENDING,
- MGCP_CONN_RECV_SEND);
- mgcp_bridge(peer, trans, BRIDGE_STATE_BRIDGE_PENDING,
- MGCP_CONN_RECV_SEND);
- break;
- default:
- LOGP(DMGCP, LOGL_ERROR,
- "Unexpected bridge state: %d for %s\n",
- trans->bridge.state, vlr_subscr_name(trans->vsub));
- break;
- }
- break;
-
- case BRIDGE_STATE_BRIDGE_PENDING:
- trans->bridge.state = BRIDGE_STATE_BRIDGE_ESTABLISHED;
- break;
-
- default:
- LOGP(DMGCP, LOGL_ERROR,
- "Unexpected bridge state: %d for %s\n",
- trans->bridge.state, vlr_subscr_name(trans->vsub));
- break;
- }
+ return msc_mgcp_call_assignment(trans);
}
int msc_call_connect(struct gsm_trans *trans, uint16_t port, uint32_t ip)
{
- /* With this function we inform the MGCP-GW where (ip/port) it
+ struct in_addr addr;
+
+ /* With this function we inform the MGCP-GW where (ip/port) it
* has to send its outgoing voic traffic. The receiving end will
* usually be a PBX (e.g. Asterisk). The IP-Address we tell, will
* not only be used to direct the traffic, it will also be used
@@ -348,61 +210,35 @@
* applicable. This is usually the case when an external MNCC
* is in use */
- struct gsm_subscriber_connection *conn;
- struct mgcp_client *mgcp;
- struct msgb *msg;
-
if (!trans)
return -EINVAL;
- if (!trans->conn)
- return -EINVAL;
- if (!trans->conn->network)
- return -EINVAL;
- if (!trans->conn->network->mgw.client)
- return -EINVAL;
- mgcp = trans->conn->network->mgw.client;
-
- struct in_addr ip_addr;
- ip_addr.s_addr = ntohl(ip);
-
- conn = trans->conn;
-
- msg = mgcp_msg_mdcx(mgcp,
- conn->rtp.mgcp_rtp_endpoint,
- inet_ntoa(ip_addr), port, MGCP_CONN_RECV_SEND);
- if (mgcp_client_tx(mgcp, msg, NULL, trans))
- LOGP(DMGCP, LOGL_ERROR,
- "Failed to send MDCX message for %s\n",
- vlr_subscr_name(trans->vsub));
-
- return 0;
+ addr.s_addr = ip;
+ return msc_mgcp_call_complete(trans, port, inet_ntoa(addr));
}
int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2)
{
+ int rc;
+
if (!trans1)
return -EINVAL;
if (!trans2)
return -EINVAL;
- /* First setup as loopback and configure the counterparts' endpoints,
- * so that when transmission starts the originating addresses are
- * already known to be valid. The mgcp callback will continue. */
- mgcp_bridge(trans1, trans2, BRIDGE_STATE_LOOPBACK_PENDING,
- MGCP_CONN_LOOPBACK);
- mgcp_bridge(trans2, trans1, BRIDGE_STATE_LOOPBACK_PENDING,
- MGCP_CONN_LOOPBACK);
+ rc = msc_mgcp_call_complete(trans1, trans2->conn->rtp.local_port_cn, trans2->conn->rtp.local_addr_cn);
+ if (rc)
+ return -EINVAL;
+
+ rc = msc_mgcp_call_complete(trans2, trans1->conn->rtp.local_port_cn, trans1->conn->rtp.local_addr_cn);
+ if (rc)
+ return -EINVAL;
return 0;
}
void msc_call_release(struct gsm_trans *trans)
{
- struct msgb *msg;
- struct gsm_subscriber_connection *conn;
- struct mgcp_client *mgcp;
-
if (!trans)
return;
if (!trans->conn)
@@ -410,17 +246,5 @@
if (!trans->conn->network)
return;
- conn = trans->conn;
- mgcp = conn->network->mgw.client;
-
- /* Send DLCX */
- msg = mgcp_msg_dlcx(mgcp, conn->rtp.mgcp_rtp_endpoint,
- conn->rtp.mgcp_rtp_endpoint);
- if (mgcp_client_tx(mgcp, msg, NULL, NULL))
- LOGP(DMGCP, LOGL_ERROR,
- "Failed to send DLCX message for %s\n",
- vlr_subscr_name(trans->vsub));
-
- /* Release endpoint id */
- mgcp_client_release_endpoint(conn->rtp.mgcp_rtp_endpoint, mgcp);
+ msc_mgcp_call_release(trans);
}
diff --git a/src/libmsc/msc_mgcp.c b/src/libmsc/msc_mgcp.c
new file mode 100644
index 0000000..8dce578
--- /dev/null
+++ b/src/libmsc/msc_mgcp.c
@@ -0,0 +1,1027 @@
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info at 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/mgcp_client/mgcp_client.h>
+#include <osmocom/msc/a_iface.h>
+#include <osmocom/msc/msc_ifaces.h>
+#include <osmocom/msc/gsm_04_08.h>
+
+#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_ENDPOINT_FORMAT "%i at mgw"
+
+/* Some internal cause codes to indicate fault
+ * condition inside the FSM */
+enum int_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_NOMEM,
+ MGCP_ERR_ASSGMNT_FAIL
+};
+
+/* Human readable respresentation of the faul codes,
+ * will be displayed by handle_error() */
+static const struct value_string int_cause_codes_str[] = {
+ {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 (BSS)"},
+ {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_RAN,
+ ST_MDCX_COMPL,
+ ST_CALL,
+ ST_HALT,
+};
+
+static const struct value_string fsm_bsc_mgcp_state_names[] = {
+ {ST_CRCX_RAN, "ST_CRCX_RAN (send CRCX for RAN)"},
+ {ST_CRCX_CN, "ST_CRCX_CN (send CRCX for CN)"},
+ {ST_CRCX_COMPL, "ST_CRCX_COMPL (assigning connection to RAN)"},
+ {ST_MDCX_CN, "ST_MDCX_CN (complete connection on CN side)"},
+ {ST_MDCX_RAN, "ST_MDCX_RAN (complete connection on RAN side)"},
+ {ST_MDCX_COMPL, "ST_MDCX_COMPL (complete connection)"},
+ {ST_CALL, "ST_CALL (call in progress)"},
+ {ST_HALT, "ST_HALT (destroy state machine)"},
+ {0, NULL}
+};
+
+enum fsm_evt {
+ /* Initial event: start off the state machine */
+ EV_INIT,
+
+ /* External event: Notify that the Assignment 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: 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,
+};
+
+static const struct value_string fsm_evt_names[] = {
+ {EV_INIT, "EV_INIT (start state machine, send CRCX for RAN)"},
+ {EV_CONNECT, "EV_CONNECT (start state machine, send CRCX for RAN)"},
+ {EV_TEARDOWN, "EV_TEARDOWN (teardown all connections)"},
+ {EV_CRCX_RAN_RESP, "EV_CRCX_RAN_RESP (got CRCX reponse for RAN)"},
+ {EV_CRCX_CN_RESP, "EV_CRCX_CN_RESP (got CRCX reponse for CN)"},
+ {EV_MDCX_RAN_RESP, "EV_MDCX_RAN_RESP (got MDCX reponse for RAN)"},
+ {EV_MDCX_CN_RESP, "EV_MDCX_CN_RESP (got MDCX reponse for CN)"},
+ {EV_DLCX_ALL_RESP, "EV_DLCX_ALL_RESP (got DLCX reponse for RAN/CN)"},
+ {0, NULL}
+};
+
+/* A general error handler function. On error we still have an interest to
+ * remove a half open connection (if possible). This function will execute
+ * a controlled jump to the DLCX phase. From there, the FSM will then just
+ * continue like the call were ended normally */
+static void handle_error(struct mgcp_ctx *mgcp_ctx, enum int_cause_code cause)
+{
+ struct osmo_fsm_inst *fi;
+
+ OSMO_ASSERT(mgcp_ctx);
+ fi = mgcp_ctx->fsm;
+ OSMO_ASSERT(fi);
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "fsm-state: %s\n", get_value_string(fsm_bsc_mgcp_state_names, fi->state));
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "%s -- graceful shutdown...\n",
+ get_value_string(int_cause_codes_str, 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);
+}
+
+/* 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);
+
+ LOGPFSML(fi, LOGL_ERROR,
+ "timeout (T%i) in state %s, tearing down...\n",
+ fi->T, get_value_string(fsm_bsc_mgcp_state_names, fi->state));
+
+ 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);
+
+ /* 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 {
+ /* Note: 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 gsm_trans *trans;
+ 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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ mgcp_ctx->rtp_endpoint = mgcp_client_next_endpoint(mgcp);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "CRCX/RAN: creating connection for the RAN side on " "MGW endpoint:%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 */
+ LOGPFSML(fi, LOGL_DEBUG, "CRCX/RAN: transmitting MGCP message to MGW...\n");
+ 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);
+ return;
+}
+
+/* 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 (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "CRCX/RAN: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ 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 */
+ strncpy(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;
+ strncpy(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);
+
+ return;
+}
+
+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 gsm_trans *trans;
+ 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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ 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:%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 */
+ LOGPFSML(fi, LOGL_DEBUG, "CRCX/CN: transmitting MGCP message to MGW...\n");
+ 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);
+ return;
+}
+
+/* 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 (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "CRCX/CN: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ 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 */
+ strncpy(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;
+ strncpy(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);
+
+ return;
+}
+
+/* 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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ 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) {
+ /* Assign a voice channel via RANAP on 3G */
+ if (msc_iu_rab_act_cs(trans)) {
+ handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
+ return;
+ }
+ } else if (conn->via_ran == RAN_GERAN_A) {
+ /* Assign a voice channel via A on 2G */
+ if (a_iface_tx_assignment(trans)) {
+ handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
+ return;
+ }
+ } else {
+ /* Someone forgot to set via_ran properly.
+ * This would point to some serious bug */
+ OSMO_ASSERT(false);
+ return;
+ }
+
+ /* Respond back to MNCC (if requested) */
+ if (trans->tch_rtp_create) {
+ if (gsm48_tch_rtp_create(trans)) {
+ handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
+ return;
+ }
+ }
+
+ /* Note: When we reach this point than 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, 0, 0);
+ return;
+}
+
+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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ 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:%x...\n", mgcp_ctx->rtp_endpoint);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "MDCX/CN: remote leg expects RTP input on address %s:%u\n", 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 */
+ LOGPFSML(fi, LOGL_DEBUG, "MDCX/CN: transmitting MGCP message to MGW...\n");
+ 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_RAN, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+ return;
+}
+
+/* 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;
+ 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 (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "MDCX/CN: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ 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);
+ return;
+}
+
+static void mgw_mdcx_ran_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_MDCX_RAN: check MGW reseponse, 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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ switch (event) {
+ case EV_MDCX_CN_RESP:
+ 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:%x...\n", mgcp_ctx->rtp_endpoint);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "MDCX/RAN: RAN expects RTP input on address %s:%u\n", 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 */
+ LOGPFSML(fi, LOGL_DEBUG, "MDCX/RAN: transmitting MGCP message to MGW...\n");
+ 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_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+ return;
+}
+
+/* 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;
+ 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 (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "MDCX/RAN: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ 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);
+ return;
+}
+
+/* Callback for ST_MDCX_COMPL: check MGW response */
+static void fsm_mdcx_compl_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+ OSMO_ASSERT(mgcp_ctx);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ switch (event) {
+ case EV_MDCX_RAN_RESP:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ LOGPFSML(fi, LOGL_ERROR, "call active, waiting for teardown...\n");
+ osmo_fsm_inst_state_chg(fi, ST_CALL, 0, 0);
+ return;
+}
+
+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 gsm_trans *trans;
+ 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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "DLCX: removing connection for the RAN and CN side on MGW endpoint:%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 */
+ LOGPFSML(fi, LOGL_DEBUG, "DLCX: transmitting MGCP message to MGW...\n");
+ 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);
+ return;
+}
+
+/* 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;
+ struct gsm_trans *trans;
+
+ OSMO_ASSERT(mgcp_ctx);
+ trans = mgcp_ctx->trans;
+ OSMO_ASSERT(trans);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "DLCX: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ 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);
+ return;
+}
+
+/* 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);
+
+ LOGPFSML(fi, LOGL_DEBUG,
+ "fsm-state: %s, fsm-event: %s\n",
+ get_value_string(fsm_bsc_mgcp_state_names, fi->state), get_value_string(fsm_evt_names, event));
+
+ LOGPFSML(fi, LOGL_DEBUG, "state machine halted\n");
+
+ /* Destroy the state machine and all context information */
+ osmo_fsm_inst_free(mgcp_ctx->fsm);
+ mgcp_ctx->fsm = NULL;
+
+ return;
+}
+
+static struct osmo_fsm_state fsm_msc_mgcp_states[] = {
+
+ /* Startup state machine, send CRCX for RAN side. */
+ [ST_CRCX_RAN] = {
+ .in_event_mask = (1 << EV_INIT),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_CRCX_CN),
+ .name = "ST_CRCX_RAN",
+ .action = fsm_crcx_ran_cb,
+ },
+ /* When the response to the CRCX is received, then proceed with sending
+ the CRCX for CN side */
+ [ST_CRCX_CN] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_CRCX_RAN_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_CRCX_COMPL),
+ .name = "ST_CRCX_CN",
+ .action = fsm_crcx_cn_cb,
+ },
+ /* Complete the CRCX phase by starting the assignment. Depending on
+ * the RAT, 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 = (1 << EV_TEARDOWN) | (1 << EV_CRCX_CN_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_MDCX_CN),
+ .name = "ST_CRCX_COMPL",
+ .action = fsm_crcx_compl,
+ },
+ /* Wait for MSC to complete the assignment request, when complete,
+ * we will enter the MDCX phaseby sending an MDCX for the CN side
+ * to the MGW */
+ [ST_MDCX_CN] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_CONNECT),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_MDCX_RAN),
+ .name = "ST_MDCX_CN",
+ .action = fsm_mdcx_cn_cb,
+ },
+ /* When the response for the MDCX is received, send the MDCX for the
+ * RAN side to the MGW */
+ [ST_MDCX_RAN] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_MDCX_CN_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_MDCX_COMPL),
+ .name = "ST_MDCX_RAN",
+ .action = fsm_mdcx_ran_cb,
+ },
+ /* The MDCX phase is complete when the response is received from the
+ * MGW. The call is now active */
+ [ST_MDCX_COMPL] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_MDCX_RAN_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL),
+ .name = "ST_MDCX_COMPL",
+ .action = fsm_mdcx_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 = (1 << EV_TEARDOWN),
+ .out_state_mask = (1 << ST_HALT),
+ .name = "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 = (1 << EV_TEARDOWN) | (1 << EV_DLCX_ALL_RESP),
+ .out_state_mask = 0,
+ .name = "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 the 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;
+
+ if (!trans) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid transaction, call assignment failed\n");
+ return -EINVAL;
+ }
+ if (!trans->conn) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid conn, call assignment failed\n");
+ return -EINVAL;
+ }
+
+ conn = trans->conn;
+ mgcp = conn->network->mgw.client;
+ OSMO_ASSERT(mgcp);
+
+ conn->rtp.mgcp_ctx = NULL;
+
+#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 */
+ /* FIXME: use apporpiate talloc context */
+ mgcp_ctx = talloc_zero(NULL, struct mgcp_ctx);
+ OSMO_ASSERT(mgcp_ctx);
+
+ /* FIXME: use apporpiate talloc context (third parameter) */
+ 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;
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MGW handler fsm created\n");
+ 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;
+ 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;
+
+ if (!addr) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid remote call leg address, call completion failed\n");
+ return -EINVAL;
+ }
+ if (port == 0) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid remote call leg port, call completion failed\n");
+ return -EINVAL;
+ }
+ if (!trans) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid transaction, call completion failed\n");
+ return -EINVAL;
+ }
+ if (!trans->conn) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid conn, call completion failed\n");
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid mgcp context, call completion failed\n");
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx->fsm) {
+ LOGP(DMGCP, LOGL_ERROR, "no FSM, call completion failed\n");
+ return -EINVAL;
+ }
+
+ mgcp_ctx = trans->conn->rtp.mgcp_ctx;
+
+ /* The FSM should already 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, "invalid call state, call completion failed\n");
+ return -EINVAL;
+ }
+
+ conn = trans->conn;
+ strncpy(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);
+
+ return 0;
+}
+
+/* Release ongoing call
+ * Parameter:
+ * trans: transaction context
+ * Returns -EINVAL on error, 0 on success */
+int msc_mgcp_call_release(struct gsm_trans *trans)
+{
+ struct mgcp_ctx *mgcp_ctx;
+
+ if (!trans) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid transaction, call release failed\n");
+ return -EINVAL;
+ }
+
+ if (!trans->conn) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid conn, call completion failed\n");
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx) {
+ LOGP(DMGCP, LOGL_ERROR, "invalid mgcp context, call completion failed\n");
+ return -EINVAL;
+ }
+ if (!trans->conn->rtp.mgcp_ctx->fsm) {
+ LOGP(DMGCP, LOGL_ERROR, "no FSM, call completion failed\n");
+ return -EINVAL;
+ }
+
+ mgcp_ctx = trans->conn->rtp.mgcp_ctx;
+
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+
+ return 0;
+}
--
To view, visit https://gerrit.osmocom.org/4980
To unsubscribe, visit https://gerrit.osmocom.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ieea9630358b3963261fa1993cf1f3b563ff23538
Gerrit-PatchSet: 1
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Owner: dexter <pmaier at sysmocom.de>