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.orgHello Neels Hofmeyr, Jenkins Builder, I'd like you to reexamine a change. Please visit https://gerrit.osmocom.org/4980 to look at the new patch set (#4). 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/iucs.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/iucs.c M src/libmsc/msc_ifaces.c A src/libmsc/msc_mgcp.c 12 files changed, 1,024 insertions(+), 308 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-msc refs/changes/80/4980/4 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 e0fbcec..bdbb448 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/iucs.h b/include/osmocom/msc/iucs.h index b7d6064..9c6c858 100644 --- a/include/osmocom/msc/iucs.h +++ b/include/osmocom/msc/iucs.h @@ -5,3 +5,5 @@ struct gsm_subscriber_connection *subscr_conn_lookup_iu(struct gsm_network *network, struct ranap_ue_conn_ctx *ue); + +int iu_rab_act_cs(struct gsm_trans *trans); diff --git a/include/osmocom/msc/msc_ifaces.h b/include/osmocom/msc/msc_ifaces.h index 0592c07..ca25e9d 100644 --- a/include/osmocom/msc/msc_ifaces.h +++ b/include/osmocom/msc/msc_ifaces.h @@ -37,7 +37,3 @@ enum gsm48_reject_value value); int msc_tx_common_id(struct gsm_subscriber_connection *conn); -int msc_call_assignment(struct gsm_trans *trans); -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); diff --git a/include/osmocom/msc/msc_mgcp.h b/include/osmocom/msc/msc_mgcp.h new file mode 100644 index 0000000..138538b --- /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_LENGTH]; + char conn_id_cn[MGCP_CONN_ID_LENGTH]; + + /* 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 bdfef87..d117b31 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..438b7cf 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); + osmo_strlcpy(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 5fab409..39cfd44 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -78,6 +78,7 @@ #include <osmocom/msc/msc_ifaces.h> #include <osmocom/msc/a_iface.h> +#include <osmocom/msc/msc_mgcp.h> #include <assert.h> @@ -1329,7 +1330,7 @@ gsm48_stop_cc_timer(trans); /* Make sure call also gets released on the mgcp side */ - msc_call_release(trans); + msc_mgcp_call_release(trans); /* send release to L4, if callref still exists */ if (trans->callref) { @@ -1385,6 +1386,23 @@ return 0; } +/* helper function for tch_bridge() to bridge the RTP Voice streams also */ +static int rtp_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2) +{ + int rc; + 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; +} + /* bridge channels of two transactions */ static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge) { @@ -1400,7 +1418,7 @@ /* Which subscriber do we want to track trans1 or trans2? */ log_set_context(LOG_CTX_VLR_SUBSCR, trans1->vsub); - return msc_call_bridge(trans1, trans2); + return rtp_bridge(trans1, trans2); } static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) @@ -1743,7 +1761,7 @@ /* Assign call (if not done yet) */ if (trans->assignment_done == false) { - rc = msc_call_assignment(trans); + rc = msc_mgcp_call_assignment(trans); trans->assignment_done = true; } else @@ -1785,7 +1803,7 @@ /* Assign call (if not done yet) */ if (trans->assignment_done == false) { - rc = msc_call_assignment(trans); + rc = msc_mgcp_call_assignment(trans); trans->assignment_done = true; } else @@ -2669,8 +2687,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 @@ -2710,15 +2728,15 @@ trans->conn->mncc_rtp_bridge = 1; - /* When we call msc_call_assignment() we will trigger, depending + /* When we call msc_mgcp_call_assignment() we will trigger, depending * on the RAN type the call assignment on the A or Iu interface. - * msc_call_assignment() also takes care about sending the CRCX + * msc_mgcp_call_assignment() also takes care about sending the CRCX * command to the MGCP-GW. The CRCX will return the port number, * where the PBX (e.g. Asterisk) will send its RTP stream to. We * have to return this port number back to the MNCC by sending * it back with the TCH_RTP_CREATE message. To make sure that * this message is sent AFTER the response to CRCX from the - * MGCP-GW has arrived, we need will instruct msc_call_assignment() + * MGCP-GW has arrived, we need will instruct msc_mgcp_call_assignment() * to take care of this by setting trans->tch_rtp_create to true. * This will make sure that gsm48_tch_rtp_create() (below) is * called as soon as the local port number has become known. */ @@ -2726,7 +2744,7 @@ /* Assign call (if not done yet) */ if (trans->assignment_done == false) { - rc = msc_call_assignment(trans); + rc = msc_mgcp_call_assignment(trans); trans->assignment_done = true; } else @@ -2753,6 +2771,7 @@ { struct gsm_trans *trans; struct gsm_mncc_rtp *rtp = arg; + struct in_addr addr; /* Find callref */ trans = trans_find_by_callref(net, rtp->callref); @@ -2768,8 +2787,8 @@ return 0; } - msc_call_connect(trans, rtp->port, rtp->ip); - return 0; + addr.s_addr = rtp->ip; + return msc_mgcp_call_complete(trans, rtp->port, inet_ntoa(addr)); } static struct downstate { diff --git a/src/libmsc/iucs.c b/src/libmsc/iucs.c index c89e412..60e2b1b 100644 --- a/src/libmsc/iucs.c +++ b/src/libmsc/iucs.c @@ -187,3 +187,39 @@ return rc; } + +int iu_rab_act_cs(struct gsm_trans *trans) +{ + struct gsm_subscriber_connection *conn + struct msgb *msg; + bool use_x213_nsap; + 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); + + LOGP(DIUCS, LOGL_DEBUG, "Assigning RAB: conn_id=%u, rab_id=%d," + " rtp=%x:%u, use_x213_nsap=%d\n", conn_id, rab_id, rtp_ip, + rtp_port, use_x213_nsap); + + msg = ranap_new_msg_rab_assign_voice(rab_id, rtp_ip, rtp_port, + use_x213_nsap); + msg->l2h = msg->data; + + if (ranap_iu_rab_act(uectx, msg)) + LOGP(DIUCS, LOGL_ERROR, "Failed to send RAB Assignment:" + " conn_id=%d rab_id=%d rtp=%x:%u\n", + conn_id, rab_id, rtp_ip, rtp_port); + return 0; +} + diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c index e29fe0e..250b4ec 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" @@ -140,287 +142,4 @@ DEBUGP(DIUCS, "%s: tx CommonID %s\n", vlr_subscr_name(conn->vsub), conn->vsub->imsi); 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) -{ -#ifdef BUILD_IU - struct msgb *msg; - bool use_x213_nsap; - uint32_t conn_id = uectx->conn_id; - - use_x213_nsap = (uectx->rab_assign_addr_enc == RANAP_NSAP_ADDR_ENC_X213); - - LOGP(DIUCS, LOGL_DEBUG, "Assigning RAB: conn_id=%u, rab_id=%d," - " rtp=%x:%u, use_x213_nsap=%d\n", conn_id, rab_id, rtp_ip, - rtp_port, use_x213_nsap); - - msg = ranap_new_msg_rab_assign_voice(rab_id, rtp_ip, rtp_port, - use_x213_nsap); - msg->l2h = msg->data; - - if (ranap_iu_rab_act(uectx, msg)) - LOGP(DIUCS, LOGL_ERROR, "Failed to send RAB Assignment:" - " conn_id=%d rab_id=%d rtp=%x:%u\n", - conn_id, rab_id, rtp_ip, rtp_port); - return 0; -#else - LOGP(DMSC, LOGL_ERROR, "Cannot send Iu RAB Assignment: built without Iu support\n"); - return -ENOTSUP; -#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; - } -} - -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 - * 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 - * as a filter to make sure only RTP packets from the right - * remote end will reach the BSS. This is also the reason why - * inbound audio will not work until this step is performed */ - - /* NOTE: This function is used when msc_call_bridge(), is not - * 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; -} - -int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2) -{ - 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); - - 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) - return; - 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); } diff --git a/src/libmsc/msc_mgcp.c b/src/libmsc/msc_mgcp.c new file mode 100644 index 0000000..de1e72f --- /dev/null +++ b/src/libmsc/msc_mgcp.c @@ -0,0 +1,893 @@ +/* (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> +#include <osmocom/msc/iucs.h> +#include <osmocom/msc/vlr.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_ENDPOINT_FORMAT "%x at 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_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 (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, +}; + +enum msc_mgcp_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, +}; + +/* 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 msc_mgcp_cause_code cause) +{ + struct osmo_fsm_inst *fi; + + OSMO_ASSERT(mgcp_ctx); + fi = mgcp_ctx->fsm; + OSMO_ASSERT(fi); + + LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "%s -- graceful shutdown...\n", + get_value_string(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, 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) { + /* 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 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 */ + 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 */ + 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 */ + 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); +} + +/* 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); +} + +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); + + 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: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 */ + 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); +} + +/* 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_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); + + 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 */ + 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); + + LOGPFSML(fi, LOGL_DEBUG, "state machine halted\n"); + + /* Destroy the state machine and all context information */ + osmo_fsm_inst_free(mgcp_ctx->fsm); + talloc_free(mgcp_ctx); +} + +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 CRCX is received, then proceed with sending + the CRCX for CN side */ + [ST_CRCX_CN] = { + .in_event_mask = S(EV_TEARDOWN) | 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, 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_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 phaseby sending an MDCX for the CN side + * to the MGW */ + [ST_MDCX_CN] = { + .in_event_mask = S(EV_TEARDOWN) | S(EV_CONNECT), + .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_RAN), + .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN) | S(EV_MDCX_CN_RESP), + .out_state_mask = S(ST_HALT) | S(ST_CALL) | S(ST_MDCX_COMPL), + .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN) | S(EV_MDCX_RAN_RESP), + .out_state_mask = S(ST_HALT) | S(ST_CALL), + .name = OSMO_STRINGIFY(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 = S(EV_TEARDOWN), + .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_DLCX_ALL_RESP), + .out_state_mask = 0, + .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 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 completion failed\n", + vlr_subscr_name(trans->vsub)); + 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; + 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; + + 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 (!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 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); + + 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->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; + + 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: newpatchset Gerrit-Change-Id: Ieea9630358b3963261fa1993cf1f3b563ff23538 Gerrit-PatchSet: 4 Gerrit-Project: osmo-msc Gerrit-Branch: master Gerrit-Owner: dexter <pmaier at sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: Neels Hofmeyr <nhofmeyr at sysmocom.de>