aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc
diff options
context:
space:
mode:
authorPhilipp Maier <pmaier@sysmocom.de>2017-09-27 15:51:34 +0200
committerHarald Welte <laforge@gnumonks.org>2017-11-07 20:57:51 +0000
commit39c609b7c924524172ad311bdf89f92b7ccf175a (patch)
tree23de9db3438dfcaf88ad27f082dbbf0dea140769 /src/osmo-bsc
parent9eb208fcfb75eedd2b1a45a2aa67893ce4726404 (diff)
mgcp: use osmo-mgw to switch RTP streams
osmo-bsc currently negotiates the RTP stream directly with the BTS and reports back the RTP IP/Port on the BTS. This works fine for a single BTS, but for Handover the port/ip pointing to the MSC side must not change, so an entity in between the BTSs and the MSC is required. Integrate the mgcp-client and use osmo-mgw to switch the RTP streams. Depends: osmo-mgw Ib5fcc72775bf72b489ff79ade36fb345d8d20736 Depends: osmo-mgw I44b338b09de45e1675cedf9737fa72dde72e979a Depends: osmo-mgw I29c5e2fb972896faeb771ba040f015592487fcbe Change-Id: Ia2882b7ca31a3219c676986e85045fa08a425d7a
Diffstat (limited to 'src/osmo-bsc')
-rw-r--r--src/osmo-bsc/Makefile.am3
-rw-r--r--src/osmo-bsc/osmo_bsc_audio.c50
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c103
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c13
-rw-r--r--src/osmo-bsc/osmo_bsc_mgcp.c1136
-rw-r--r--src/osmo-bsc/osmo_bsc_sigtran.c5
-rw-r--r--src/osmo-bsc/osmo_bsc_vty.c6
7 files changed, 1252 insertions, 64 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index dfc4defcb..7db698c1d 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -15,6 +15,7 @@ AM_CFLAGS = \
$(COVERAGE_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(LIBOSMOSIGTRAN_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
@@ -30,6 +31,7 @@ osmo_bsc_SOURCES = \
osmo_bsc_vty.c \
osmo_bsc_api.c \
osmo_bsc_grace.c \
+ osmo_bsc_mgcp.c \
osmo_bsc_msc.c \
osmo_bsc_sigtran.c \
osmo_bsc_filter.c \
@@ -53,4 +55,5 @@ osmo_bsc_LDADD = \
$(COVERAGE_LDFLAGS) \
$(LIBOSMOABIS_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
+ $(LIBOSMOMGCPCLIENT_LIBS) \
$(NULL)
diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c
index 94aa350d3..0c11b85e3 100644
--- a/src/osmo-bsc/osmo_bsc_audio.c
+++ b/src/osmo-bsc/osmo_bsc_audio.c
@@ -29,47 +29,10 @@
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/gsm0808_utils.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
#include <arpa/inet.h>
-/* Generate and send assignment complete message */
-static int send_aoip_ass_compl(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan)
-{
- struct msgb *resp;
- struct sockaddr_storage rtp_addr;
- struct sockaddr_in rtp_addr_in;
- struct gsm0808_speech_codec sc;
-
- OSMO_ASSERT(lchan->abis_ip.ass_compl.valid == true);
-
- /* 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 = htons(lchan->abis_ip.bound_port);
- rtp_addr_in.sin_addr.s_addr = htonl(lchan->abis_ip.bound_ip);
- memset(&rtp_addr, 0, sizeof(rtp_addr));
- memcpy(&rtp_addr, &rtp_addr_in, sizeof(rtp_addr_in));
-
- /* Extrapolate speech codec from speech mode */
- gsm0808_speech_codec_from_chan_type(&sc, lchan->abis_ip.ass_compl.speech_mode);
-
- /* Generate message */
- resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause,
- lchan->abis_ip.ass_compl.chosen_channel,
- lchan->abis_ip.ass_compl.encr_alg_id,
- lchan->abis_ip.ass_compl.speech_mode,
- &rtp_addr,
- &sc,
- NULL);
-
- if (!resp) {
- LOGP(DMSC, LOGL_ERROR, "Failed to generate assignment completed message!\n"); \
- return -EINVAL;
- }
-
- return osmo_bsc_sigtran_send(conn->sccp_con, resp);
-}
-
static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
@@ -117,18 +80,19 @@ static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
/* NOTE: When an ho_lchan exists, the MDCX is part of an
* handover operation (intra-bsc). This means we will not
* inform the MSC about the event, which means that no
- * assignment complete message is transmitted */
- LOGP(DMSC, LOGL_INFO," RTP connection handover complete\n");
+ * assignment complete message is transmitted, we just
+ * inform the logic that controls the MGW about the new
+ * connection info */
+ LOGP(DMSC, LOGL_INFO,"RTP connection handover initiated...\n");
+ mgcp_handover(con->sccp_con->mgcp_ctx, con->ho_lchan);
} else if (is_ipaccess_bts(con->bts) && con->sccp_con->rtp_ip) {
/* NOTE: This is only relevant on AoIP networks with
* IPA based base stations. See also osmo_bsc_api.c,
* function bsc_assign_compl() */
LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL (POSTPONED)\n");
- if (send_aoip_ass_compl(con, lchan) != 0)
- return -EINVAL;
+ mgcp_ass_complete(con->sccp_con->mgcp_ctx, lchan);
}
break;
- break;
}
return 0;
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index 93e9274a0..9f8032e9e 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -25,6 +25,7 @@
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/legacy_mgcp/mgcp.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/gsm_04_08_utils.h>
@@ -351,14 +352,28 @@ static int bssmap_handle_clear_command(struct osmo_bsc_sccp_con *conn,
conn->conn = NULL;
}
- /* send the clear complete message */
+ /* generate the clear complete message */
resp = gsm0808_create_clear_complete();
if (!resp) {
LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n");
return -1;
}
- osmo_bsc_sigtran_send(conn, resp);
+ if (conn->mgcp_ctx) {
+ /* NOTE: This is the AoIP case, osmo-bsc has to negotiate with
+ * the MGCP-GW. For this an mgcp_ctx should be created that
+ * contains the FSM and some system data. When the connection
+ * is removed from the MGCP-GW, then osmo_bsc_sigtran_send()
+ * calls osmo_bsc_sigtran_send(). */
+ mgcp_clear_complete(conn->mgcp_ctx, resp);
+ } else {
+ /* NOTE: This is the SCCP-Lite case, since we do not handle
+ * the MGCP-GW switching ourselves, we may skip everything
+ * that is MGCP-GW related and sent the clear complete message
+ * directly */
+ osmo_bsc_sigtran_send(conn, resp);
+ }
+
return 0;
}
@@ -456,7 +471,6 @@ static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
int port, full_rate = -1;
bool aoip = false;
struct sockaddr_storage rtp_addr;
- struct sockaddr_in *rtp_addr_in;
struct gsm0808_channel_type ct;
struct gsm0808_speech_codec_list scl;
struct gsm0808_speech_codec_list *scl_ptr = NULL;
@@ -561,29 +575,40 @@ static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
get_value_string(gsm48_chan_mode_names, chan_mode),
ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
- if (aoip == false) {
- /* map it to a MGCP Endpoint and a RTP port */
+ /* Forward the assingment request to lower layers */
+ if (aoip) {
+ /* Store network side RTP connection information, we will
+ * process this address later after we have established an RTP
+ * connection to the BTS. This is just for organizational
+ * reasons, functional wise it would not matter when exactly
+ * the network side RTP connection is made, as long it is made
+ * before we return with the assignment complete message. */
+ memcpy(&conn->aoip_rtp_addr_remote, &rtp_addr, sizeof(rtp_addr));
+
+ /* Create an assignment request using the MGCP fsm. This FSM
+ * is directly started when its created (now) and will also
+ * take care about the further processing (creating RTP
+ * endpoints, calling gsm0808_assign_req(), rsponding to
+ * the assignment request etc... */
+ conn->mgcp_ctx = mgcp_assignm_req(msc->network, msc->network->mgw.client, conn, chan_mode, full_rate);
+ if (!conn->mgcp_ctx) {
+ LOGP(DMSC, LOGL_ERROR, "MGCP GW failure, rejecting assignment... (id=%i)\n", conn->conn_id);
+ goto reject;
+ }
+
+ /* We now may return here, the FSM will do all further work */
+ return 0;
+ } else {
+ /* Note: In the sccp-lite case we to not perform any mgcp operation,
+ * (the MSC does that for us). We set conn->rtp_ip to 0 and check
+ * on this later. By this we know that we have to behave accordingly
+ * to sccp-lite. */
port = mgcp_timeslot_to_endpoint(multiplex, timeslot);
conn->rtp_port = rtp_calculate_port(port, msc->rtp_base);
conn->rtp_ip = 0;
- } else {
- /* use address / port supplied with the AoIP
- * transport address element */
- if (rtp_addr.ss_family == AF_INET) {
- rtp_addr_in = (struct sockaddr_in *)&rtp_addr;
- conn->rtp_port = osmo_ntohs(rtp_addr_in->sin_port);
- memcpy(&conn->rtp_ip, &rtp_addr_in->sin_addr.s_addr,
- IP_V4_ADDR_LEN);
- conn->rtp_ip = osmo_ntohl(conn->rtp_ip);
- } else {
- LOGP(DMSC, LOGL_ERROR,
- "Unsopported addressing scheme. (supports only IPV4)\n");
- goto reject;
- }
+ return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
}
- return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
-
reject:
resp =
gsm0808_create_assignment_failure
@@ -759,3 +784,39 @@ int bsc_handle_dt(struct osmo_bsc_sccp_con *conn,
return -1;
}
+
+/* Generate and send assignment complete message */
+int bssmap_send_aoip_ass_compl(struct gsm_lchan *lchan)
+{
+ struct msgb *resp;
+ struct gsm0808_speech_codec sc;
+ struct gsm_subscriber_connection *conn;
+
+ conn = lchan->conn;
+
+ OSMO_ASSERT(lchan->abis_ip.ass_compl.valid);
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(conn->sccp_con);
+
+ LOGP(DMSC, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp_con->conn_id);
+
+ /* Extrapolate speech codec from speech mode */
+ gsm0808_speech_codec_from_chan_type(&sc, lchan->abis_ip.ass_compl.speech_mode);
+
+ /* Generate message */
+ resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause,
+ lchan->abis_ip.ass_compl.chosen_channel,
+ lchan->abis_ip.ass_compl.encr_alg_id,
+ lchan->abis_ip.ass_compl.speech_mode,
+ &conn->sccp_con->aoip_rtp_addr_local,
+ &sc,
+ NULL);
+
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n",
+ conn->sccp_con->conn_id);
+ return -EINVAL;
+ }
+
+ return osmo_bsc_sigtran_send(conn->sccp_con, resp);
+}
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index 730e1db48..5d25701bb 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -44,6 +44,7 @@
#include <osmocom/abis/abis.h>
#include <osmocom/sccp/sccp.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
#define _GNU_SOURCE
#include <getopt.h>
@@ -206,6 +207,9 @@ int main(int argc, char **argv)
exit(1);
}
+ bsc_gsmnet->mgw.conf = talloc_zero(bsc_gsmnet, struct mgcp_client_conf);
+ mgcp_client_conf_init(bsc_gsmnet->mgw.conf);
+
bts_init();
libosmo_abis_init(tall_bsc_ctx);
@@ -274,6 +278,15 @@ int main(int argc, char **argv)
}
}
+ bsc_gsmnet->mgw.client = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf);
+
+ if (mgcp_client_connect(bsc_gsmnet->mgw.client)) {
+ LOGP(DNM, LOGL_ERROR, "MGW connect failed at (%s:%u)\n",
+ bsc_gsmnet->mgw.conf->remote_addr,
+ bsc_gsmnet->mgw.conf->remote_port);
+ exit(1);
+ }
+
if (osmo_bsc_sigtran_init(&bsc_gsmnet->bsc_data->mscs) != 0) {
LOGP(DNM, LOGL_ERROR, "Failed to initalize sigtran backhaul.\n");
exit(1);
diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c
new file mode 100644
index 000000000..a7b6b4128
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_mgcp.c
@@ -0,0 +1,1136 @@
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/mgcp_client/mgcp_client.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/core/byteswap.h>
+#include <arpa/inet.h>
+
+#define CONN_ID_BTS 1
+#define CONN_ID_NET 2
+
+#define MGCP_MGW_TIMEOUT 4 /* in seconds */
+#define MGCP_MGW_TIMEOUT_TIMER_NR 1
+#define MGCP_BSS_TIMEOUT 4 /* in seconds */
+#define MGCP_BSS_TIMEOUT_TIMER_NR 2
+
+#define MGCP_ENDPOINT_FORMAT "%i@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_ASSGMNT_FAIL,
+ MGCP_ERR_UNSUPP_ADDR_FMT,
+ MGCP_ERR_BSS_TIMEOUT,
+ MGCP_ERR_NOMEM
+};
+
+/* Human readable respresentation of the faul codes,
+ * will be displayed by handle_error() */
+static const struct value_string 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 (BSS)"},
+ {MGCP_ERR_ASSGMNT_FAIL, "assignment failure (BSS)"},
+ {MGCP_ERR_UNSUPP_ADDR_FMT, "unsupported network address format used (MSC)"},
+ {MGCP_ERR_BSS_TIMEOUT, "assignment could not be completed in time (BSS)"},
+ {MGCP_ERR_NOMEM, "out of memory"},
+ {0, NULL}
+};
+
+enum fsm_bsc_mgcp_states {
+ ST_CRCX_BTS,
+ ST_ASSIGN_PROC,
+ ST_MDCX_BTS,
+ ST_CRCX_NET,
+ ST_ASSIGN_COMPL,
+ ST_CALL,
+ ST_MDCX_BTS_HO,
+ ST_HALT
+};
+
+static const struct value_string fsm_bsc_mgcp_state_names[] = {
+ {ST_CRCX_BTS, "ST_CRCX_BTS (send CRCX for BTS)"},
+ {ST_ASSIGN_PROC, "ST_ASSIGN_PROC (continue assignment)"},
+ {ST_MDCX_BTS, "ST_MDCX_BTS (send MDCX for BTS)"},
+ {ST_CRCX_NET, "ST_CRCX_NET (send CRCX for NET)"},
+ {ST_ASSIGN_COMPL, "ST_ASSIGN_COMPL (complete assignment)"},
+ {ST_CALL, "ST_CALL (call in progress)"},
+ {ST_MDCX_BTS_HO, "ST_MDCX_BTS_HO (handover to new BTS)"},
+ {ST_HALT, "ST_HALT (destroy state machine)"},
+ {0, NULL}
+};
+
+enum fsm_evt {
+ /* Initial event: start off the state machine */
+ EV_INIT,
+
+ /* External event: Assignment complete, event is issued shortly before
+ * the assignment complete message is sent via the A-Interface */
+ EV_ASS_COMPLETE,
+
+ /* External event: Teardown event, this event is used to notify the end
+ * of a call. It is also issued in case of errors to teardown a half
+ * open connection. */
+ EV_TEARDOWN,
+
+ /* External event: Handover event, this event notifies the FSM that a
+ * handover is required. The FSM will then perform an extra MDCX to
+ * configure the new connection data at the MGW. The only valid state
+ * where a Handover event can be received is ST_CALL. */
+ EV_HANDOVER,
+
+ /* Internal event: The mgcp_gw has sent its CRCX response for
+ * the BTS side */
+ EV_CRCX_BTS_RESP,
+
+ /* Internal event: The mgcp_gw has sent its MDCX response for
+ * the BTS side */
+ EV_MDCX_BTS_RESP,
+
+ /* Internal event: The mgcp_gw has sent its CRCX response for
+ * the NET side */
+ EV_CRCX_NET_RESP,
+
+ /* Internal event: The mgcp_gw has sent its DLCX response for
+ * the NET and BTS side */
+ EV_DLCX_ALL_RESP,
+
+ /* Internal event: The mgcp_gw has responded to the (Handover-)
+ MDCX that has been send to update the BTS connection. */
+ EV_MDCX_BTS_HO_RESP,
+};
+
+static const struct value_string fsm_evt_names[] = {
+ {EV_INIT, "EV_INIT (start state machine, send CRCX for BTS)"},
+ {EV_ASS_COMPLETE, "EV_ASS_COMPLETE (assignment complete)"},
+ {EV_TEARDOWN, "EV_TEARDOWN (teardown all connections)"},
+ {EV_HANDOVER, "EV_HANDOVER (handover bts connection)"},
+ {EV_CRCX_BTS_RESP, "EV_CRCX_BTS_RESP (got CRCX reponse for BTS)"},
+ {EV_MDCX_BTS_RESP, "EV_MDCX_BTS_RESP (got MDCX reponse for BTS)"},
+ {EV_CRCX_NET_RESP, "EV_CRCX_NET_RESP (got CRCX reponse for NET)"},
+ {EV_DLCX_ALL_RESP, "EV_DLCX_ALL_RESP (got DLCX reponse for BTS/NET)"},
+ {EV_MDCX_BTS_HO_RESP, "EV_MDCX_BTS_HO_RESP (got MDCX reponse for BTS Handover)"},
+ {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;
+ struct osmo_bsc_sccp_con *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+
+ 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);
+}
+
+static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CRCX_BTS: startup state machine send out CRCX for BTS side */
+static void fsm_crcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct osmo_bsc_sccp_con *conn;
+ struct msgb *msg;
+ struct mgcp_msg mgcp_msg;
+ struct mgcp_client *mgcp;
+ uint16_t rtp_endpoint;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ LOGPFSML(mgcp_ctx->fsm, 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));
+
+ rtp_endpoint = mgcp_client_next_endpoint(mgcp);
+ mgcp_ctx->rtp_endpoint = rtp_endpoint;
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "creating connection for the BTS side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+ /* Generate MGCP message string */
+ mgcp_msg = (struct mgcp_msg) {
+ .verb = MGCP_VERB_CRCX,
+ .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+ MGCP_MSG_PRESENCE_CONN_MODE),
+ .call_id = conn->conn_id,
+ .conn_id = CONN_ID_BTS,
+ .conn_mode = MGCP_CONN_LOOPBACK
+ };
+ if (snprintf(mgcp_msg.endpoint, MGCP_ENDPOINT_MAXLEN, MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
+ MGCP_ENDPOINT_MAXLEN) {
+ handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
+ return;
+ }
+ msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+ OSMO_ASSERT(msg);
+
+ /* Transmit MGCP message to MGW */
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: transmitting MGCP message to MGW...\n");
+ rc = mgcp_client_tx(mgcp, msg, crcx_for_bts_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_ASSIGN_PROC, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for BTS associated CRCX */
+static void crcx_for_bts_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+ int rc;
+ struct osmo_bsc_sccp_con *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "CRCX/BTS: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "CRCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+ return;
+ }
+
+ rc = mgcp_response_parse_params(r);
+ if (rc) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/BTS: Cannot parse response\n");
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+ /* Set the connection details in the conn struct. The code that
+ * controls the BTS via RSL will take these values and signal them
+ * to the BTS via RSL/IPACC */
+ conn->rtp_port = r->audio_port;
+ conn->rtp_ip = osmo_ntohl(inet_addr(r->audio_ip));
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_BTS_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_ASSIGN_PROC: An mgcp response has been received, proceed
+ * with the assignment request */
+static void fsm_proc_assignmnent_req_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct osmo_bsc_sccp_con *conn;
+ enum gsm48_chan_mode chan_mode;
+ bool full_rate;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+
+ LOGPFSML(mgcp_ctx->fsm, 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_BTS_RESP:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ OSMO_ASSERT(conn->conn);
+ chan_mode = mgcp_ctx->chan_mode;
+ full_rate = mgcp_ctx->full_rate;
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MGW proceeding assignment request...\n");
+ rc = gsm0808_assign_req(conn->conn, chan_mode, full_rate);
+
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_ASSGMNT_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, ST_MDCX_BTS, MGCP_BSS_TIMEOUT, MGCP_BSS_TIMEOUT_TIMER_NR);
+}
+
+static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_MDCX_BTS: When the BSS has completed the assignment,
+ * proceed with updating the connection for the BTS side */
+static void fsm_mdcx_bts_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct osmo_bsc_sccp_con *conn;
+ struct gsm_lchan *lchan;
+ struct msgb *msg;
+ struct mgcp_msg mgcp_msg;
+ struct mgcp_client *mgcp;
+ uint16_t rtp_endpoint;
+ struct in_addr addr;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+
+ LOGPFSML(mgcp_ctx->fsm, 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_ASS_COMPLETE:
+ break;
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ }
+
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+ lchan = mgcp_ctx->lchan;
+ OSMO_ASSERT(lchan);
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "BSS has completed the assignment, now prceed with MDCX towards BTS...\n");
+
+ rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "completing connection for the BTS side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+ addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "BTS expects RTP input on address %s:%u\n", inet_ntoa(addr), lchan->abis_ip.bound_port);
+
+ /* Generate MGCP message string */
+ mgcp_msg = (struct mgcp_msg) {
+ .verb = MGCP_VERB_MDCX,
+ .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+ MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
+ .call_id = conn->conn_id,
+ .conn_id = CONN_ID_BTS,
+ .conn_mode = MGCP_CONN_RECV_SEND,
+ .audio_ip = inet_ntoa(addr),
+ .audio_port = lchan->abis_ip.bound_port
+ };
+ if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
+ sizeof(mgcp_msg.endpoint)) {
+ handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
+ return;
+ }
+ msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+ OSMO_ASSERT(msg);
+
+ /* Transmit MGCP message to MGW */
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS: transmitting MGCP message to MGW...\n");
+ rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_CRCX_NET, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for BTS associated MDCX */
+static void mdcx_for_bts_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+ int rc;
+ struct in_addr addr;
+ struct gsm_lchan *lchan;
+
+ OSMO_ASSERT(mgcp_ctx);
+ lchan = mgcp_ctx->lchan;
+ OSMO_ASSERT(lchan);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "MDCX/BTS: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "MDCX/BTS: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+ return;
+ }
+
+ rc = mgcp_response_parse_params(r);
+ if (rc) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "MDCX/BTS: Cannot parse MDCX response\n");
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+ addr.s_addr = lchan->abis_ip.bound_ip;
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "MDCX/BTS: corresponding lchan has been bound to address %s:%u\n",
+ inet_ntoa(addr), lchan->abis_ip.bound_port);
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_RESP, mgcp_ctx);
+}
+
+static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Callback for ST_CRCX_NET: An mgcp response has been received, proceed... */
+static void fsm_crcx_net_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct osmo_bsc_sccp_con *conn;
+ struct msgb *msg;
+ struct mgcp_msg mgcp_msg;
+ struct mgcp_client *mgcp;
+ uint16_t rtp_endpoint;
+ struct sockaddr_in *sin;
+ char *addr;
+ uint16_t port;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ LOGPFSML(mgcp_ctx->fsm, 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));
+
+ rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "creating connection for the NET side on " "MGW endpoint:%x...\n", rtp_endpoint);
+
+ /* Currently we only have support for IPv4 in our MGCP software, the
+ * AoIP part is ready to support IPv6 in theory, because the IE
+ * parser/generator uses sockaddr_storage for the AoIP transport
+ * identifier. However, the MGCP-GW does not support IPv6 yet. This is
+ * why we stop here in case some MSC tries to signal IPv6 AoIP
+ * transport identifiers */
+ if (conn->aoip_rtp_addr_remote.ss_family != AF_INET) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "endpoint:%x MSC uses unsupported address format in AoIP transport identifier -- aborting...\n",
+ rtp_endpoint);
+ handle_error(mgcp_ctx, MGCP_ERR_UNSUPP_ADDR_FMT);
+ return;
+ }
+
+ sin = (struct sockaddr_in *)&conn->aoip_rtp_addr_remote;
+ addr = inet_ntoa(sin->sin_addr);
+ port = osmo_ntohs(sin->sin_port);
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MSC expects RTP input on address %s:%u\n", addr, port);
+
+ /* Generate MGCP message string */
+ mgcp_msg = (struct mgcp_msg) {
+ .verb = MGCP_VERB_CRCX,
+ .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+ MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT),
+ .call_id = conn->conn_id,
+ .conn_id = CONN_ID_NET,
+ .conn_mode = MGCP_CONN_RECV_SEND,
+ .audio_ip = addr,
+ .audio_port = port
+ };
+ if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
+ sizeof(mgcp_msg.endpoint)) {
+ handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
+ return;
+ }
+ msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+ OSMO_ASSERT(msg);
+
+ /* Transmit MGCP message to MGW */
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: transmitting MGCP message to MGW...\n");
+ rc = mgcp_client_tx(mgcp, msg, crcx_for_net_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_ASSIGN_COMPL, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for MGCP-Client: handle response for NET associated CRCX */
+static void crcx_for_net_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+ int rc;
+ struct osmo_bsc_sccp_con *conn;
+ struct gsm_lchan *lchan;
+ struct sockaddr_in *sin;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+ lchan = mgcp_ctx->lchan;
+ OSMO_ASSERT(lchan);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "CRCX/NET: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "CRCX/NET: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+ return;
+ }
+
+ rc = mgcp_response_parse_params(r);
+ if (rc) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "CRCX/NET: Cannot parse CRCX response\n");
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_INVAL_RESP);
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "CRCX/NET: MGW responded with address %s:%u\n", r->audio_ip, r->audio_port);
+
+ /* Store address */
+ sin = (struct sockaddr_in *)&conn->aoip_rtp_addr_local;
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = inet_addr(r->audio_ip);
+ sin->sin_port = osmo_ntohs(r->audio_port);
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_CRCX_NET_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_ASSIGN_COMPL: Send back assignment complete and wait until the call ends */
+static void fsm_send_assignment_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct gsm_lchan *lchan;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ LOGPFSML(mgcp_ctx->fsm, 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) {
+ default:
+ handle_error(mgcp_ctx, MGCP_ERR_UNEXP_TEARDOWN);
+ return;
+ case EV_CRCX_NET_RESP:
+ break;
+ }
+
+ lchan = mgcp_ctx->lchan;
+ OSMO_ASSERT(lchan);
+
+ /* Send assignment completion message via AoIP, this will complete
+ * the circuit. The message will also contain the port and IP-Address
+ * where the MGW expects the RTP input from the MSC side */
+ bssmap_send_aoip_ass_compl(lchan);
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "call in progress, waiting for call end...\n");
+
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_CALL, 0, 0);
+}
+
+static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv);
+static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv);
+
+/* Helper function to perform a connection teardown. This function may be
+ * called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state
+ * change to ST_HALT when teardown is done. */
+static void handle_teardown(struct mgcp_ctx *mgcp_ctx)
+{
+ struct osmo_bsc_sccp_con *conn;
+ struct msgb *msg;
+ struct mgcp_msg mgcp_msg;
+ struct mgcp_client *mgcp;
+ uint16_t rtp_endpoint;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "DLCX: removing connection for the BTS and NET side on MGW endpoint:%x...\n", rtp_endpoint);
+
+ /* We now relase the endpoint back to the pool in order to allow
+ * other connections to use this endpoint */
+ mgcp_client_release_endpoint(rtp_endpoint, mgcp);
+
+ /* Generate MGCP message string */
+ mgcp_msg = (struct mgcp_msg) {
+ .verb = MGCP_VERB_DLCX,
+ .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID),
+ .call_id = conn->conn_id
+ };
+ if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
+ sizeof(mgcp_msg.endpoint)) {
+ handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
+ return;
+ }
+ msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+ OSMO_ASSERT(msg);
+
+ /* Transmit MGCP message to MGW */
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "DLCX: transmitting MGCP message to MGW...\n");
+ rc = mgcp_client_tx(mgcp, msg, dlcx_for_all_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_HALT, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Helper function to perform a handover (MDCX). This function may be
+ * called from ST_CALL and ST_MDCX_BTS_HO only. It will perform a state
+ * change to ST_CALL when teardown is done. */
+static void handle_handover(struct mgcp_ctx *mgcp_ctx)
+{
+ struct osmo_bsc_sccp_con *conn;
+ struct msgb *msg;
+ struct mgcp_msg mgcp_msg;
+ struct mgcp_client *mgcp;
+ struct gsm_lchan *ho_lchan;
+ uint16_t rtp_endpoint;
+ struct in_addr addr;
+ int rc;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+ ho_lchan = mgcp_ctx->ho_lchan;
+ OSMO_ASSERT(ho_lchan);
+
+ rtp_endpoint = mgcp_ctx->rtp_endpoint;
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "MDCX/BTS/HO: handover connection from old BTS to new BTS side on MGW endpoint:%x...\n", rtp_endpoint);
+
+ addr.s_addr = osmo_ntohl(ho_lchan->abis_ip.bound_ip);
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG,
+ "MDCX/BTS/HO: new BTS expects RTP input on address %s:%u\n", inet_ntoa(addr),
+ ho_lchan->abis_ip.bound_port);
+
+ /* Generate MGCP message string */
+ mgcp_msg = (struct mgcp_msg) {
+ .verb = MGCP_VERB_MDCX,
+ .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID |
+ MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP |
+ MGCP_MSG_PRESENCE_AUDIO_PORT),.call_id = conn->conn_id,.conn_id = CONN_ID_BTS,
+ .conn_mode = MGCP_CONN_RECV_SEND,
+ .audio_ip = inet_ntoa(addr),
+ .audio_port = ho_lchan->abis_ip.bound_port};
+ if (snprintf(mgcp_msg.endpoint, sizeof(mgcp_msg.endpoint), MGCP_ENDPOINT_FORMAT, rtp_endpoint) >=
+ sizeof(mgcp_msg.endpoint)) {
+ handle_error(mgcp_ctx, MGCP_ERR_NOMEM);
+ return;
+ }
+ msg = mgcp_msg_gen(mgcp, &mgcp_msg);
+ OSMO_ASSERT(msg);
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS/HO: transmitting MGCP message to MGW...\n");
+ rc = mgcp_client_tx(mgcp, msg, mdcx_for_bts_ho_resp_cb, mgcp_ctx);
+ if (rc < 0) {
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_TX_FAIL);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR);
+}
+
+/* Callback for ST_CALL: Handle call teardown and Handover */
+static void fsm_active_call_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = data;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ LOGPFSML(mgcp_ctx->fsm, 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_TEARDOWN:
+ handle_teardown(mgcp_ctx);
+ break;
+ case EV_HANDOVER:
+ handle_handover(mgcp_ctx);
+ break;
+ }
+
+}
+
+/* Callback for MGCP-Client: handle response for BTS/Handover associated MDCX */
+static void mdcx_for_bts_ho_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "MDCX/BTS/HO: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "MDCX/BTS/HO: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+ handle_error(mgcp_ctx, MGCP_ERR_MGW_FAIL);
+ return;
+ }
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_MDCX_BTS_HO_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_MDCX_BTS_HO: Complete updating the connection data after
+ * handoverin the call to another BTS */
+static void fsm_complete_handover(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+
+ OSMO_ASSERT(mgcp_ctx);
+
+ LOGPFSML(mgcp_ctx->fsm, 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_BTS_HO_RESP:
+ /* The response from the MGW arrived, the connection pointing
+ * towards the BTS is now updated, so we now change back to
+ * ST_CALL, where we will wait for the call-end (or another
+ * handover) */
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MDCX/BTS/HO: handover done, waiting for call end...\n");
+ osmo_fsm_inst_state_chg(mgcp_ctx->fsm, ST_CALL, 0, 0);
+ break;
+ case EV_HANDOVER:
+ /* This handles the rare, but possible situation where another
+ * handover is happening while we still wait for the the MGW to
+ * complete the current one. In this case we will stop waiting
+ * for the response and directly move on with that second
+ * handover */
+ handle_handover(mgcp_ctx);
+ break;
+ case EV_TEARDOWN:
+ /* It may happen that the BSS wants to teardown all connections
+ * while we are still waiting for the MGW to respond. In this
+ * case we start to teard down the connection immediately */
+ handle_teardown(mgcp_ctx);
+ break;
+ }
+}
+
+/* Callback for MGCP-Client: handle response for NET associated CRCX */
+static void dlcx_for_all_resp_cb(struct mgcp_response *r, void *priv)
+{
+ struct mgcp_ctx *mgcp_ctx = priv;
+ struct osmo_bsc_sccp_con *conn;
+ struct mgcp_client *mgcp;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+ mgcp = mgcp_ctx->mgcp;
+ OSMO_ASSERT(mgcp);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "DLCX: late MGW response, FSM already terminated -- ignoring...\n");
+ return;
+ }
+
+ /* Note: We check the return code, but in case of an error there is
+ * not much that can be done to recover. However, at least we tryed
+ * to remove the connection (if there was even any) */
+ if (r->head.response_code != 200) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR,
+ "DLCX: response yields error: %d %s\n", r->head.response_code, r->head.comment);
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "DLCX: MGW has acknowledged the removal of the connections\n");
+
+ /* Notify the FSM that we got the response. */
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_DLCX_ALL_RESP, mgcp_ctx);
+}
+
+/* Callback for ST_HALT: Terminate the state machine */
+static void fsm_halt_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct mgcp_ctx *mgcp_ctx = (struct mgcp_ctx *)data;
+ struct osmo_bsc_sccp_con *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+
+ LOGPFSML(mgcp_ctx->fsm, 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));
+
+ /* Send pending sigtran message */
+ if (mgcp_ctx->resp) {
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "sending pending sigtran response message...\n");
+ osmo_bsc_sigtran_send(conn, mgcp_ctx->resp);
+ mgcp_ctx->resp = NULL;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, 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;
+}
+
+/* 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(mgcp_ctx->fsm, LOGL_ERROR,
+ "timeout (T%i) in state %s, attempting graceful teardown...\n",
+ fi->T, get_value_string(fsm_bsc_mgcp_state_names, fi->state));
+
+ /* Ensure that no sigtran response, is present. Otherwiese we might try
+ * to send a sigtran response when the sccp connection is already freed. */
+ mgcp_ctx->resp = NULL;
+
+ if (fi->T == MGCP_MGW_TIMEOUT_TIMER_NR) {
+ /* Note: We were unable to communicate with the MGCP-GW,
+ * unfortunately there is no meaningful action we can take
+ * now other than giving up. */
+ LOGPFSML(mgcp_ctx->fsm, LOGL_ERROR, "graceful teardown not possible, terminating...\n");
+
+ /* 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(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+ } else if (fi->T == MGCP_BSS_TIMEOUT_TIMER_NR)
+ /* Note: If the logic that controls the BSS is unable to
+ * negotiate a connection, we presumably still have a
+ * working connection to the MGCP-GW, we will try to
+ * shut down gracefully. */
+ handle_error(mgcp_ctx, MGCP_ERR_BSS_TIMEOUT);
+ else {
+ /* Note: Ther must not be any unsolicited timers
+ * in this FSM. If so, we have serious problem. */
+ OSMO_ASSERT(false);
+ }
+
+ return 0;
+}
+
+static struct osmo_fsm_state fsm_bsc_mgcp_states[] = {
+
+ /* Startup state machine, send CRCX to BTS. */
+ [ST_CRCX_BTS] = {
+ .in_event_mask = (1 << EV_INIT),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_ASSIGN_PROC),
+ .name = "ST_CRCX_BTS",
+ .action = fsm_crcx_bts_cb,
+ },
+
+ /* When the CRCX response for the BTS side is received, then
+ * proceed the assignment on the BSS side. */
+ [ST_ASSIGN_PROC] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_CRCX_BTS_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_MDCX_BTS),
+ .name = "ST_ASSIGN_PROC",
+ .action = fsm_proc_assignmnent_req_cb,
+ },
+
+ /* When the BSS has processed the assignment request,
+ * then send the MDCX command for the BTS side in order to
+ * update the connections with the actual PORT/IP where the
+ * BTS expects the RTP input. */
+ [ST_MDCX_BTS] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_ASS_COMPLETE),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_CRCX_NET),
+ .name = "ST_MDCX_BTS",
+ .action = fsm_mdcx_bts_cb,
+ },
+
+ /* When the MDCX response for the BTS siede is received, then
+ * directly proceed with sending the CRCX command to connect the
+ * network side. This is done in one phase (no MDCX needed). */
+ [ST_CRCX_NET] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_MDCX_BTS_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL) | (1 << ST_ASSIGN_COMPL),
+ .name = "ST_CRCX_NET",
+ .action = fsm_crcx_net_cb,
+ },
+
+ /* When the CRCX response for the NET side is received. Then
+ * send the assignment complete message via the A-Interface and
+ * enter wait state in order to wait for the end of the call. */
+ [ST_ASSIGN_COMPL] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_CRCX_NET_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL),
+ .name = "ST_ASSIGN_COMPL",
+ .action = fsm_send_assignment_complete,
+ },
+
+ /* When the call ends, remove all RTP connections from the
+ * MGCP-GW by sending a wildcarded DLCX. In case of a handover,
+ * go for an extra MDCX to update the connection and land in
+ * this state again when done. */
+ [ST_CALL] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_HANDOVER),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_MDCX_BTS_HO),
+ .name = "ST_CALL",
+ .action = fsm_active_call_cb,
+ },
+
+ /* A handover is in progress. When the response to the respective
+ * MDCX is received, then go back to ST_CALL and wait for the
+ * call end */
+ [ST_MDCX_BTS_HO] = {
+ .in_event_mask = (1 << EV_TEARDOWN) | (1 << EV_HANDOVER) | (1 << EV_MDCX_BTS_HO_RESP),
+ .out_state_mask = (1 << ST_HALT) | (1 << ST_CALL),
+ .name = "ST_MDCX_BTS_HO",
+ .action = fsm_complete_handover,
+ },
+
+ /* When the MGCP_GW 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_bsc_mgcp = {
+ .name = "MGW",
+ .states = fsm_bsc_mgcp_states,
+ .num_states = ARRAY_SIZE(fsm_bsc_mgcp_states),
+ .log_subsys = DMGCP,
+ .timer_cb = fsm_timeout_cb,
+};
+
+/* Notify that the a new call begins. This will create a connection for the
+ * BTS on the MGCP-GW and set up the port numbers in struct osmo_bsc_sccp_con.
+ * After that gsm0808_assign_req() to proceed.
+ * Parameter:
+ * ctx: talloc context
+ * network: associated gsm network
+ * conn: associated sccp connection
+ * chan_mode: channel mode (system data, passed through)
+ * full_rate: full rate flag (system data, passed through)
+ * Returns an mgcp_context that contains system data and the OSMO-FSM */
+struct mgcp_ctx *mgcp_assignm_req(void *ctx, struct mgcp_client *mgcp, struct osmo_bsc_sccp_con *conn,
+ enum gsm48_chan_mode chan_mode, bool full_rate)
+{
+ struct mgcp_ctx *mgcp_ctx;
+ char name[32];
+ static bool fsm_registered = false;
+
+ OSMO_ASSERT(mgcp);
+ OSMO_ASSERT(conn);
+
+ if(snprintf(name, sizeof(name), "MGW_%i", conn->conn_id) >= sizeof(name))
+ return NULL;
+
+ /* Register the fsm description (if not already done) */
+ if (fsm_registered == false) {
+ osmo_fsm_register(&fsm_bsc_mgcp);
+ fsm_registered = true;
+ }
+
+ /* Allocate and configure a new fsm instance */
+ mgcp_ctx = talloc_zero(ctx, struct mgcp_ctx);
+ OSMO_ASSERT(mgcp_ctx);
+
+ mgcp_ctx->fsm = osmo_fsm_inst_alloc(&fsm_bsc_mgcp, NULL, ctx, LOGL_DEBUG, name);
+ OSMO_ASSERT(mgcp_ctx->fsm);
+ mgcp_ctx->fsm->priv = mgcp_ctx;
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "MGW handler fsm created\n");
+ mgcp_ctx->mgcp = mgcp;
+ mgcp_ctx->conn = conn;
+ mgcp_ctx->chan_mode = chan_mode;
+ mgcp_ctx->full_rate = full_rate;
+
+ /* start state machine */
+ OSMO_ASSERT(mgcp_ctx->fsm->state == ST_CRCX_BTS);
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_INIT, mgcp_ctx);
+
+ return mgcp_ctx;
+}
+
+/* Notify that the call has ended, remove all connections from the MGCP-GW,
+ * then send the clear complete message and destroy the FSM instance
+ * Parameter:
+ * mgcp_ctx: context information (FSM, and pointer to external system data)
+ * respmgcp_ctx: pending clear complete message to send via A-Interface */
+void mgcp_clear_complete(struct mgcp_ctx *mgcp_ctx, struct msgb *resp)
+{
+ struct osmo_bsc_sccp_con *conn;
+
+ OSMO_ASSERT(mgcp_ctx);
+ OSMO_ASSERT(resp);
+ conn = mgcp_ctx->conn;
+ OSMO_ASSERT(conn);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "clear completion attemted on already terminated FSM -- forwarding directly...\n");
+ osmo_bsc_sigtran_send(conn, resp);
+ mgcp_ctx->resp = NULL;
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating call end...\n");
+
+ mgcp_ctx->resp = resp;
+
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_TEARDOWN, mgcp_ctx);
+}
+
+/* Notify that the BSS ready, send the assingnment complete message when the
+ * mgcp connection is completed
+ * Parameter:
+ * mgcp_ctx: context information (FSM, and pointer to external system data)
+ * lchan: needed for sending the assignment complete message via A-Interface */
+void mgcp_ass_complete(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *lchan)
+{
+ OSMO_ASSERT(mgcp_ctx);
+ OSMO_ASSERT(lchan);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "assignment completion attemted on already terminated FSM -- ignored\n");
+ mgcp_ctx->lchan = NULL;
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating assignment completion...\n");
+
+ mgcp_ctx->lchan = lchan;
+
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_ASS_COMPLETE, mgcp_ctx);
+
+ return;
+}
+
+/* Notify that the call got handovered to another BTS, update the connection
+ * that is pointing to the BTS side with the connection data for the new bts.
+ * Parameter:
+ * mgcp_ctx: context information (FSM, and pointer to external system data)
+ * ho_lchan: the lchan on the new BTS */
+void mgcp_handover(struct mgcp_ctx *mgcp_ctx, struct gsm_lchan *ho_lchan)
+{
+ OSMO_ASSERT(mgcp_ctx);
+ OSMO_ASSERT(ho_lchan);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_ERROR, "handover attemted on already terminated FSM -- ignored\n");
+ mgcp_ctx->ho_lchan = NULL;
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "bss is indicating handover...\n");
+
+ mgcp_ctx->ho_lchan = ho_lchan;
+
+ osmo_fsm_inst_dispatch(mgcp_ctx->fsm, EV_HANDOVER, mgcp_ctx);
+
+ return;
+}
+
+/* Free an existing mgcp context gracefully
+ * Parameter:
+ * mgcp_ctx: context information (FSM, and pointer to external system data) */
+void mgcp_free_ctx(struct mgcp_ctx *mgcp_ctx)
+{
+ OSMO_ASSERT(mgcp_ctx);
+
+ if (mgcp_ctx->fsm == NULL) {
+ LOGP(DMGCP, LOGL_DEBUG, "fsm already terminated, freeing only related context information...\n");
+ talloc_free(mgcp_ctx);
+ return;
+ }
+
+ LOGPFSML(mgcp_ctx->fsm, LOGL_DEBUG, "terminating fsm and freeing related context information...\n");
+
+ osmo_fsm_inst_free(mgcp_ctx->fsm);
+ talloc_free(mgcp_ctx);
+}
diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c
index 951061a56..2ba777ed9 100644
--- a/src/osmo-bsc/osmo_bsc_sigtran.c
+++ b/src/osmo-bsc/osmo_bsc_sigtran.c
@@ -33,6 +33,7 @@
#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/a_reset.h>
#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/osmo_bsc_mgcp.h>
/* A pointer to a list with all involved MSCs
* (a copy of the pointer location submitted with osmo_bsc_sigtran_init() */
@@ -384,6 +385,10 @@ int osmo_bsc_sigtran_del_conn(struct osmo_bsc_sccp_con *conn)
a_reset_conn_fail(conn->msc->a.reset);
}
+ /* Remove mgcp context if existant */
+ if (conn->mgcp_ctx)
+ mgcp_free_ctx(conn->mgcp_ctx);
+
llist_del(&conn->entry);
talloc_free(conn);
diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c
index f816ae428..ca4709759 100644
--- a/src/osmo-bsc/osmo_bsc_vty.c
+++ b/src/osmo-bsc/osmo_bsc_vty.c
@@ -29,6 +29,8 @@
#include <osmocom/core/talloc.h>
#include <osmocom/vty/logging.h>
#include <osmocom/sccp/sccp_types.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
#include <time.h>
@@ -972,6 +974,8 @@ DEFUN(logging_fltr_imsi,
int bsc_vty_init_extra(void)
{
+ struct gsm_network *net = bsc_gsmnet;
+
install_element(CONFIG_NODE, &cfg_net_msc_cmd);
install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
@@ -1034,5 +1038,7 @@ int bsc_vty_init_extra(void)
install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+ mgcp_client_vty_init(net, MSC_NODE, net->mgw.conf);
+
return 0;
}