aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc_nat/bsc_mgcp_utils.c
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2017-07-04 23:08:44 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2017-08-27 03:52:43 +0200
commit218e4b4aa0fc6de842ff820dec8e97d1f083268a (patch)
tree268a6e509270b1c80a36dd1a526da41a9b01a8e0 /src/osmo-bsc_nat/bsc_mgcp_utils.c
parent5ea6bfce56d6ae7be6d85e05b5e4eaebc94d1005 (diff)
move openbsc/* to repos root
This is the first step in creating this repository from the legacy openbsc.git. Like all other Osmocom repositories, keep the autoconf and automake files in the repository root. openbsc.git has been the sole exception, which ends now. Change-Id: I9c6f2a448d9cb1cc088cf1cf6918b69d7e69b4e7
Diffstat (limited to 'src/osmo-bsc_nat/bsc_mgcp_utils.c')
-rw-r--r--src/osmo-bsc_nat/bsc_mgcp_utils.c1152
1 files changed, 1152 insertions, 0 deletions
diff --git a/src/osmo-bsc_nat/bsc_mgcp_utils.c b/src/osmo-bsc_nat/bsc_mgcp_utils.c
new file mode 100644
index 000000000..48847865c
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_mgcp_utils.c
@@ -0,0 +1,1152 @@
+/**
+ * This file contains helper routines for MGCP Gateway handling.
+ *
+ * The first thing to remember is that each BSC has its own namespace/range
+ * of endpoints. Whenever a BSSMAP ASSIGNMENT REQUEST is received this code
+ * will be called to select an endpoint on the BSC. The mapping from original
+ * multiplex/timeslot to BSC multiplex'/timeslot' will be stored.
+ *
+ * The second part is to take messages on the public MGCP GW interface
+ * and forward them to the right BSC. This requires the MSC to first
+ * assign the timeslot. This assumption has been true so far. We are using
+ * the policy_cb of the MGCP protocol code to decide if the request should
+ * be immediately answered or delayed. An extension "Z: noanswer" is used
+ * to request the BSC to not respond. This is saving some bytes of bandwidth
+ * and as we are using TCP to forward the message we know it will arrive.
+ * The mgcp_do_read method reads these messages and hands them to the protocol
+ * parsing code which will call the mentioned policy_cb. The bsc_mgcp_forward
+ * method is used on the way back from the BSC to the network.
+ *
+ * The third part is to patch messages forwarded to the BSC. This includes
+ * the endpoint number, the ports to be used inside the SDP file and maybe
+ * some other bits.
+ *
+ */
+/*
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * 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 <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_callstats.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/osmux.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+static void send_direct(struct bsc_nat *nat, struct msgb *output)
+{
+ if (osmo_wqueue_enqueue(&nat->mgcp_cfg->gw_fd, output) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to queue MGCP msg.\n");
+ msgb_free(output);
+ }
+}
+
+static void mgcp_queue_for_call_agent(struct bsc_nat *nat, struct msgb *output)
+{
+ if (nat->mgcp_ipa)
+ bsc_nat_send_mgcp_to_msc(nat, output);
+ else
+ send_direct(nat, output);
+}
+
+int bsc_mgcp_nr_multiplexes(int max_endpoints)
+{
+ int div = max_endpoints / 32;
+
+ if ((max_endpoints % 32) != 0)
+ div += 1;
+
+ return div;
+}
+
+static int bsc_init_endps_if_needed(struct bsc_connection *con)
+{
+ int multiplexes;
+
+ /* we have done that */
+ if (con->_endpoint_status)
+ return 0;
+
+ /* we have no config... */
+ if (!con->cfg)
+ return -1;
+
+ multiplexes = bsc_mgcp_nr_multiplexes(con->cfg->max_endpoints);
+ con->number_multiplexes = multiplexes;
+ con->max_endpoints = con->cfg->max_endpoints;
+ con->_endpoint_status = talloc_zero_array(con, char, 32 * multiplexes + 1);
+ return con->_endpoint_status == NULL;
+}
+
+static int bsc_assign_endpoint(struct bsc_connection *bsc, struct nat_sccp_connection *con)
+{
+ int multiplex;
+ int timeslot;
+ const int number_endpoints = bsc->max_endpoints;
+ int i;
+
+ mgcp_endpoint_to_timeslot(bsc->last_endpoint, &multiplex, &timeslot);
+ timeslot += 1;
+
+ for (i = 0; i < number_endpoints; ++i) {
+ int endpoint;
+
+ /* Wrap around timeslots */
+ if (timeslot == 0)
+ timeslot = 1;
+
+ if (timeslot == 0x1f) {
+ timeslot = 1;
+ multiplex += 1;
+ }
+
+ /* Wrap around the multiplex */
+ if (multiplex >= bsc->number_multiplexes)
+ multiplex = 0;
+
+ endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+ /* Now check if we are allowed to assign this one */
+ if (endpoint >= bsc->max_endpoints) {
+ multiplex = 0;
+ timeslot = 1;
+ endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+ }
+
+
+ if (bsc->_endpoint_status[endpoint] == 0) {
+ bsc->_endpoint_status[endpoint] = 1;
+ con->bsc_endp = endpoint;
+ bsc->last_endpoint = endpoint;
+ return 0;
+ }
+
+ timeslot += 1;
+ }
+
+ return -1;
+}
+
+static uint16_t create_cic(int endpoint)
+{
+ int timeslot, multiplex;
+
+ mgcp_endpoint_to_timeslot(endpoint, &multiplex, &timeslot);
+ return (multiplex << 5) | (timeslot & 0x1f);
+}
+
+int bsc_mgcp_assign_patch(struct nat_sccp_connection *con, struct msgb *msg)
+{
+ struct nat_sccp_connection *mcon;
+ struct tlv_parsed tp;
+ uint16_t cic;
+ uint8_t timeslot;
+ uint8_t multiplex;
+ unsigned int endp;
+
+ if (!msg->l3h) {
+ LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n");
+ return -1;
+ }
+
+ if (msgb_l3len(msg) < 3) {
+ LOGP(DNAT, LOGL_ERROR, "Assignment message has not enough space for GSM0808.\n");
+ return -1;
+ }
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+ LOGP(DNAT, LOGL_ERROR, "Circuit identity code not found in assignment message.\n");
+ return -1;
+ }
+
+ cic = ntohs(tlvp_val16_unal(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+ timeslot = cic & 0x1f;
+ multiplex = (cic & ~0x1f) >> 5;
+
+
+ endp = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+ if (endp >= con->bsc->nat->mgcp_cfg->trunk.number_endpoints) {
+ LOGP(DNAT, LOGL_ERROR,
+ "MSC attempted to assign bad endpoint 0x%x\n",
+ endp);
+ return -1;
+ }
+
+ /* find stale connections using that endpoint */
+ llist_for_each_entry(mcon, &con->bsc->nat->sccp_connections, list_entry) {
+ if (mcon->msc_endp == endp) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Endpoint %d was assigned to 0x%x and now 0x%x\n",
+ endp,
+ sccp_src_ref_to_int(&mcon->patched_ref),
+ sccp_src_ref_to_int(&con->patched_ref));
+ bsc_mgcp_dlcx(mcon);
+ }
+ }
+
+ con->msc_endp = endp;
+ if (bsc_init_endps_if_needed(con->bsc) != 0)
+ return -1;
+ if (bsc_assign_endpoint(con->bsc, con) != 0)
+ return -1;
+
+ /*
+ * now patch the message for the new CIC...
+ * still assumed to be one multiplex only
+ */
+ cic = htons(create_cic(con->bsc_endp));
+ memcpy((uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE),
+ &cic, sizeof(cic));
+
+ return 0;
+}
+
+static void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i)
+{
+ if (nat->bsc_endpoints[i].transaction_id) {
+ talloc_free(nat->bsc_endpoints[i].transaction_id);
+ nat->bsc_endpoints[i].transaction_id = NULL;
+ }
+
+ nat->bsc_endpoints[i].transaction_state = 0;
+ nat->bsc_endpoints[i].bsc = NULL;
+}
+
+void bsc_mgcp_free_endpoints(struct bsc_nat *nat)
+{
+ int i;
+
+ for (i = 1; i < nat->mgcp_cfg->trunk.number_endpoints; ++i){
+ bsc_mgcp_free_endpoint(nat, i);
+ mgcp_release_endp(&nat->mgcp_cfg->trunk.endpoints[i]);
+ }
+}
+
+/* send a MDCX where we do not want a response */
+static void bsc_mgcp_send_mdcx(struct bsc_connection *bsc, int port, struct mgcp_endpoint *endp)
+{
+ char buf[2096];
+ int len;
+
+ len = snprintf(buf, sizeof(buf),
+ "MDCX 23 %x@mgw MGCP 1.0\r\n"
+ "Z: noanswer\r\n"
+ "\r\n"
+ "c=IN IP4 %s\r\n"
+ "m=audio %d RTP/AVP 255\r\n",
+ port, mgcp_bts_src_addr(endp),
+ endp->bts_end.local_port);
+ if (len < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "snprintf for MDCX failed.\n");
+ return;
+ }
+
+ bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+static void bsc_mgcp_send_dlcx(struct bsc_connection *bsc, int endpoint, int trans)
+{
+ char buf[2096];
+ int len;
+
+ /*
+ * The following is a bit of a spec violation. According to the
+ * MGCP grammar the transaction id is are upto 9 digits but we
+ * prefix it with an alpha numeric value so we can easily recognize
+ * it as a response.
+ */
+ len = snprintf(buf, sizeof(buf),
+ "DLCX nat-%u %x@mgw MGCP 1.0\r\n",
+ trans, endpoint);
+ if (len < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+ return;
+ }
+
+ bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+void bsc_mgcp_init(struct nat_sccp_connection *con)
+{
+ con->msc_endp = -1;
+ con->bsc_endp = -1;
+}
+
+/**
+ * This code will remember the network side of the audio statistics and
+ * once the internal DLCX response arrives this can be combined with the
+ * the BSC side and forwarded as a trap.
+ */
+static void remember_pending_dlcx(struct nat_sccp_connection *con, uint32_t transaction)
+{
+ struct bsc_nat_call_stats *stats;
+ struct bsc_connection *bsc = con->bsc;
+ struct mgcp_endpoint *endp;
+
+ stats = talloc_zero(bsc, struct bsc_nat_call_stats);
+ if (!stats) {
+ LOGP(DNAT, LOGL_NOTICE,
+ "Failed to allocate statistics for endpoint 0x%x\n",
+ con->msc_endp);
+ return;
+ }
+
+ /* take the endpoint here */
+ endp = &bsc->nat->mgcp_cfg->trunk.endpoints[con->msc_endp];
+
+ stats->remote_ref = con->remote_ref;
+ stats->src_ref = con->patched_ref;
+
+ stats->ci = endp->ci;
+ stats->bts_rtp_port = endp->bts_end.rtp_port;
+ stats->bts_addr = endp->bts_end.addr;
+ stats->net_rtp_port = endp->net_end.rtp_port;
+ stats->net_addr = endp->net_end.addr;
+
+ stats->net_ps = endp->net_end.packets;
+ stats->net_os = endp->net_end.octets;
+ stats->bts_pr = endp->bts_end.packets;
+ stats->bts_or = endp->bts_end.octets;
+ mgcp_state_calc_loss(&endp->bts_state, &endp->bts_end,
+ &stats->bts_expected, &stats->bts_loss);
+ stats->bts_jitter = mgcp_state_calc_jitter(&endp->bts_state);
+
+ stats->trans_id = transaction;
+ stats->msc_endpoint = con->msc_endp;
+
+ /*
+ * Too many pending requests.. let's remove the first two items.
+ */
+ if (!llist_empty(&bsc->pending_dlcx) &&
+ bsc->pending_dlcx_count >= bsc->cfg->max_endpoints * 3) {
+ struct bsc_nat_call_stats *tmp;
+ LOGP(DNAT, LOGL_ERROR,
+ "Too many(%d) pending DLCX responses on BSC: %d\n",
+ bsc->pending_dlcx_count, bsc->cfg->nr);
+ bsc->pending_dlcx_count -= 1;
+ tmp = (struct bsc_nat_call_stats *) bsc->pending_dlcx.next;
+ llist_del(&tmp->entry);
+ talloc_free(tmp);
+ }
+
+ bsc->pending_dlcx_count += 1;
+ llist_add_tail(&stats->entry, &bsc->pending_dlcx);
+}
+
+void bsc_mgcp_dlcx(struct nat_sccp_connection *con)
+{
+ /* send a DLCX down the stream */
+ if (con->bsc_endp != -1 && con->bsc->_endpoint_status) {
+ LOGP(DNAT, LOGL_NOTICE,
+ "Endpoint 0x%x was allocated for bsc: %d. Freeing it.\n",
+ con->bsc_endp, con->bsc->cfg->nr);
+ if (con->bsc->_endpoint_status[con->bsc_endp] != 1)
+ LOGP(DNAT, LOGL_ERROR, "Endpoint 0x%x was not in use\n", con->bsc_endp);
+ remember_pending_dlcx(con, con->bsc->next_transaction);
+ con->bsc->_endpoint_status[con->bsc_endp] = 0;
+ bsc_mgcp_send_dlcx(con->bsc, con->bsc_endp, con->bsc->next_transaction++);
+ bsc_mgcp_free_endpoint(con->bsc->nat, con->msc_endp);
+ }
+
+ bsc_mgcp_init(con);
+
+}
+
+/*
+ * Search for the pending request
+ */
+static void handle_dlcx_response(struct bsc_connection *bsc, struct msgb *msg,
+ int code, const char *transaction)
+{
+ uint32_t trans_id = UINT32_MAX;
+ uint32_t b_ps, b_os, n_pr, n_or, jitter;
+ int loss;
+ struct bsc_nat_call_stats *tmp, *stat = NULL;
+ struct ctrl_cmd *cmd;
+
+ /* parse the transaction identifier */
+ int rc = sscanf(transaction, "nat-%u", &trans_id);
+ if (rc != 1) {
+ LOGP(DNAT, LOGL_ERROR, "Can not parse transaction id: '%s'\n",
+ transaction);
+ return;
+ }
+
+ /* find the answer for the request we made */
+ llist_for_each_entry(tmp, &bsc->pending_dlcx, entry) {
+ if (trans_id != tmp->trans_id)
+ continue;
+
+ stat = tmp;
+ break;
+ }
+
+ if (!stat) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Can not find transaction for: %u\n", trans_id);
+ return;
+ }
+
+ /* attempt to parse the data now */
+ rc = mgcp_parse_stats(msg, &b_ps, &b_os, &n_pr, &n_or, &loss, &jitter);
+ if (rc != 0)
+ LOGP(DNAT, LOGL_ERROR,
+ "Can not parse connection statistics: %d\n", rc);
+
+ /* send a trap now */
+ cmd = ctrl_cmd_create(bsc, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Creating a ctrl cmd failed.\n");
+ goto free_stat;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "net.0.bsc.%d.call_stats.v2",
+ bsc->cfg->nr);
+ cmd->reply = talloc_asprintf(cmd,
+ "mg_ip_addr=%s,mg_port=%d,",
+ inet_ntoa(stat->net_addr),
+ stat->net_rtp_port);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "endpoint_ip_addr=%s,endpoint_port=%d,",
+ inet_ntoa(stat->bts_addr),
+ stat->bts_rtp_port);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "nat_pkt_in=%u,nat_pkt_out=%u,"
+ "nat_bytes_in=%u,nat_bytes_out=%u,"
+ "nat_jitter=%u,nat_pkt_lost=%d,",
+ stat->bts_pr, stat->net_ps,
+ stat->bts_or, stat->net_os,
+ stat->bts_jitter, stat->bts_loss);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "bsc_pkt_in=%u,bsc_pkt_out=%u,"
+ "bsc_bytes_in=%u,bsc_bytes_out=%u,"
+ "bsc_jitter=%u,bsc_pkt_lost=%d,",
+ n_pr, b_ps,
+ n_or, b_os,
+ jitter, loss);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "sccp_src_ref=%u,sccp_dst_ref=%u",
+ sccp_src_ref_to_int(&stat->src_ref),
+ sccp_src_ref_to_int(&stat->remote_ref));
+
+ /* send it and be done */
+ ctrl_cmd_send_to_all(bsc->nat->ctrl, cmd);
+ talloc_free(cmd);
+
+free_stat:
+ bsc->pending_dlcx_count -= 1;
+ llist_del(&stat->entry);
+ talloc_free(stat);
+}
+
+
+struct nat_sccp_connection *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint)
+{
+ struct nat_sccp_connection *con = NULL;
+ struct nat_sccp_connection *sccp;
+
+ llist_for_each_entry(sccp, &nat->sccp_connections, list_entry) {
+ if (sccp->msc_endp == -1)
+ continue;
+ if (sccp->msc_endp != endpoint)
+ continue;
+
+ con = sccp;
+ }
+
+ if (con)
+ return con;
+
+ LOGP(DMGCP, LOGL_ERROR,
+ "Failed to find the connection for endpoint: 0x%x\n", endpoint);
+ return NULL;
+}
+
+static int nat_osmux_only(struct mgcp_config *mgcp_cfg, struct bsc_config *bsc_cfg)
+{
+ if (mgcp_cfg->osmux == OSMUX_USAGE_ONLY)
+ return 1;
+ if (bsc_cfg->osmux == OSMUX_USAGE_ONLY)
+ return 1;
+ return 0;
+}
+
+static int bsc_mgcp_policy_cb(struct mgcp_trunk_config *tcfg, int endpoint, int state, const char *transaction_id)
+{
+ struct bsc_nat *nat;
+ struct bsc_endpoint *bsc_endp;
+ struct nat_sccp_connection *sccp;
+ struct mgcp_endpoint *mgcp_endp;
+ struct msgb *bsc_msg;
+
+ nat = tcfg->cfg->data;
+ bsc_endp = &nat->bsc_endpoints[endpoint];
+ mgcp_endp = &nat->mgcp_cfg->trunk.endpoints[endpoint];
+
+ if (bsc_endp->transaction_id) {
+ LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x had pending transaction: '%s'\n",
+ endpoint, bsc_endp->transaction_id);
+ talloc_free(bsc_endp->transaction_id);
+ bsc_endp->transaction_id = NULL;
+ bsc_endp->transaction_state = 0;
+ }
+ bsc_endp->bsc = NULL;
+
+ sccp = bsc_mgcp_find_con(nat, endpoint);
+
+ if (!sccp) {
+ LOGP(DMGCP, LOGL_ERROR, "Did not find BSC for change on endpoint: 0x%x state: %d\n", endpoint, state);
+
+ switch (state) {
+ case MGCP_ENDP_CRCX:
+ return MGCP_POLICY_REJECT;
+ break;
+ case MGCP_ENDP_DLCX:
+ return MGCP_POLICY_CONT;
+ break;
+ case MGCP_ENDP_MDCX:
+ return MGCP_POLICY_CONT;
+ break;
+ default:
+ LOGP(DMGCP, LOGL_FATAL, "Unhandled state: %d\n", state);
+ return MGCP_POLICY_CONT;
+ break;
+ }
+ }
+
+ /* Allocate a Osmux circuit ID */
+ if (state == MGCP_ENDP_CRCX) {
+ if (nat->mgcp_cfg->osmux && sccp->bsc->cfg->osmux) {
+ osmux_allocate_cid(mgcp_endp);
+ if (mgcp_endp->osmux.allocated_cid < 0 &&
+ nat_osmux_only(nat->mgcp_cfg, sccp->bsc->cfg)) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Rejecting usage of endpoint\n");
+ return MGCP_POLICY_REJECT;
+ }
+ }
+ }
+
+ /* we need to generate a new and patched message */
+ bsc_msg = bsc_mgcp_rewrite((char *) nat->mgcp_msg, nat->mgcp_length,
+ sccp->bsc_endp, mgcp_bts_src_addr(mgcp_endp),
+ mgcp_endp->bts_end.local_port,
+ mgcp_endp->osmux.allocated_cid,
+ &mgcp_endp->net_end.codec.payload_type,
+ nat->sdp_ensure_amr_mode_set);
+ if (!bsc_msg) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to patch the msg.\n");
+ return MGCP_POLICY_CONT;
+ }
+
+
+ bsc_endp->transaction_id = talloc_strdup(nat, transaction_id);
+ bsc_endp->transaction_state = state;
+ bsc_endp->bsc = sccp->bsc;
+
+ /* we need to update some bits */
+ if (state == MGCP_ENDP_CRCX) {
+ struct sockaddr_in sock;
+
+ /* Annotate the allocated Osmux CID until the bsc confirms that
+ * it agrees to use Osmux for this voice flow.
+ */
+ if (mgcp_endp->osmux.allocated_cid >= 0 &&
+ mgcp_endp->osmux.state != OSMUX_STATE_ENABLED) {
+ mgcp_endp->osmux.state = OSMUX_STATE_NEGOTIATING;
+ mgcp_endp->osmux.cid = mgcp_endp->osmux.allocated_cid;
+ }
+
+ socklen_t len = sizeof(sock);
+ if (getpeername(sccp->bsc->write_queue.bfd.fd, (struct sockaddr *) &sock, &len) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Can not get the peername...%d/%s\n",
+ errno, strerror(errno));
+ } else {
+ mgcp_endp->bts_end.addr = sock.sin_addr;
+ }
+
+ /* send the message and a fake MDCX to force sending of a dummy packet */
+ bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+ bsc_mgcp_send_mdcx(sccp->bsc, sccp->bsc_endp, mgcp_endp);
+ return MGCP_POLICY_DEFER;
+ } else if (state == MGCP_ENDP_DLCX) {
+ /* we will free the endpoint now and send a DLCX to the BSC */
+ msgb_free(bsc_msg);
+ bsc_mgcp_dlcx(sccp);
+
+ /* libmgcp clears the MGCP endpoint for us */
+ if (mgcp_endp->osmux.state == OSMUX_STATE_ENABLED)
+ osmux_release_cid(mgcp_endp);
+
+ return MGCP_POLICY_CONT;
+ } else {
+ bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+ return MGCP_POLICY_DEFER;
+ }
+}
+
+/*
+ * We do have a failure, free data downstream..
+ */
+static void free_chan_downstream(struct mgcp_endpoint *endp, struct bsc_endpoint *bsc_endp,
+ struct bsc_connection *bsc)
+{
+ LOGP(DMGCP, LOGL_ERROR, "No CI, freeing endpoint 0x%x in state %d\n",
+ ENDPOINT_NUMBER(endp), bsc_endp->transaction_state);
+
+ /* if a CRCX failed... send a DLCX down the stream */
+ if (bsc_endp->transaction_state == MGCP_ENDP_CRCX) {
+ struct nat_sccp_connection *con;
+ con = bsc_mgcp_find_con(bsc->nat, ENDPOINT_NUMBER(endp));
+ if (!con) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "No SCCP connection for endp 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ } else {
+ if (con->bsc == bsc) {
+ bsc_mgcp_send_dlcx(bsc, con->bsc_endp, con->bsc->next_transaction++);
+ } else {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Endpoint belongs to a different BSC\n");
+ }
+ }
+ }
+
+ bsc_mgcp_free_endpoint(bsc->nat, ENDPOINT_NUMBER(endp));
+ mgcp_release_endp(endp);
+}
+
+static void bsc_mgcp_osmux_confirm(struct mgcp_endpoint *endp, const char *str)
+{
+ unsigned int osmux_cid;
+ char *res;
+
+ res = strstr(str, "X-Osmux: ");
+ if (!res) {
+ LOGP(DMGCP, LOGL_INFO,
+ "BSC doesn't want to use Osmux, failing back to RTP\n");
+ goto err;
+ }
+
+ if (sscanf(res, "X-Osmux: %u", &osmux_cid) != 1) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to parse Osmux CID '%s'\n",
+ str);
+ goto err;
+ }
+
+ if (endp->osmux.cid != osmux_cid) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "BSC sent us wrong CID %u, we expected %u",
+ osmux_cid, endp->osmux.cid);
+ goto err;
+ }
+
+ LOGP(DMGCP, LOGL_NOTICE, "bsc accepted to use Osmux (cid=%u)\n",
+ osmux_cid);
+ endp->osmux.state = OSMUX_STATE_ACTIVATING;
+ return;
+err:
+ osmux_release_cid(endp);
+ endp->osmux.state = OSMUX_STATE_DISABLED;
+}
+
+/*
+ * We have received a msg from the BSC. We will see if we know
+ * this transaction and if it belongs to the BSC. Then we will
+ * need to patch the content to point to the local network and we
+ * need to update the I: that was assigned by the BSS.
+ *
+ * Only responses to CRCX and DLCX should arrive here. The DLCX
+ * needs to be handled specially to combine the two statistics.
+ */
+void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg)
+{
+ struct msgb *output;
+ struct bsc_endpoint *bsc_endp = NULL;
+ struct mgcp_endpoint *endp = NULL;
+ int i, code;
+ char transaction_id[60];
+
+ /* Some assumption that our buffer is big enough.. and null terminate */
+ if (msgb_l2len(msg) > 2000) {
+ LOGP(DMGCP, LOGL_ERROR, "MGCP message too long.\n");
+ return;
+ }
+
+ msg->l2h[msgb_l2len(msg)] = '\0';
+
+ if (bsc_mgcp_parse_response((const char *) msg->l2h, &code, transaction_id) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to parse response code.\n");
+ return;
+ }
+
+ for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+ if (bsc->nat->bsc_endpoints[i].bsc != bsc)
+ continue;
+ /* no one listening? a bug? */
+ if (!bsc->nat->bsc_endpoints[i].transaction_id)
+ continue;
+ if (strcmp(transaction_id, bsc->nat->bsc_endpoints[i].transaction_id) != 0)
+ continue;
+
+ endp = &bsc->nat->mgcp_cfg->trunk.endpoints[i];
+ bsc_endp = &bsc->nat->bsc_endpoints[i];
+ break;
+ }
+
+ if (!bsc_endp && strncmp("nat-", transaction_id, 4) == 0) {
+ handle_dlcx_response(bsc, msg, code, transaction_id);
+ return;
+ }
+
+ if (!bsc_endp) {
+ LOGP(DMGCP, LOGL_ERROR, "Could not find active endpoint: %s for msg: '%s'\n",
+ transaction_id, (const char *) msg->l2h);
+ return;
+ }
+
+ endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h);
+ if (endp->ci == CI_UNUSED) {
+ free_chan_downstream(endp, bsc_endp, bsc);
+ return;
+ }
+
+ if (endp->osmux.state == OSMUX_STATE_NEGOTIATING)
+ bsc_mgcp_osmux_confirm(endp, (const char *) msg->l2h);
+
+ /* If we require osmux and it is disabled.. fail */
+ if (nat_osmux_only(bsc->nat->mgcp_cfg, bsc->cfg) &&
+ endp->osmux.state == OSMUX_STATE_DISABLED) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Failed to activate osmux endpoint 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ free_chan_downstream(endp, bsc_endp, bsc);
+ return;
+ }
+
+ /* free some stuff */
+ talloc_free(bsc_endp->transaction_id);
+ bsc_endp->transaction_id = NULL;
+ bsc_endp->transaction_state = 0;
+
+ /*
+ * rewrite the information. In case the endpoint was deleted
+ * there should be nothing for us to rewrite so putting endp->rtp_port
+ * with the value of 0 should be no problem.
+ */
+ output = bsc_mgcp_rewrite((char * ) msg->l2h, msgb_l2len(msg), -1,
+ mgcp_net_src_addr(endp),
+ endp->net_end.local_port, -1,
+ &endp->bts_end.codec.payload_type,
+ bsc->nat->sdp_ensure_amr_mode_set);
+ if (!output) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to rewrite MGCP msg.\n");
+ return;
+ }
+
+ mgcp_queue_for_call_agent(bsc->nat, output);
+}
+
+int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60])
+{
+ int rc;
+ /* we want to parse two strings */
+ rc = sscanf(str, "%3d %59s\n", code, transaction) != 2;
+ transaction[59] = '\0';
+ return rc;
+}
+
+uint32_t bsc_mgcp_extract_ci(const char *str)
+{
+ unsigned int ci;
+ char *res = strstr(str, "I: ");
+ if (!res) {
+ LOGP(DMGCP, LOGL_ERROR, "No CI in msg '%s'\n", str);
+ return CI_UNUSED;
+ }
+
+ if (sscanf(res, "I: %u", &ci) != 1) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to parse CI in msg '%s'\n", str);
+ return CI_UNUSED;
+ }
+
+ return ci;
+}
+
+/**
+ * Create a new MGCPCommand based on the input and endpoint from a message
+ */
+static void patch_mgcp(struct msgb *output, const char *op, const char *tok,
+ int endp, int len, int cr, int osmux_cid)
+{
+ int slen;
+ int ret;
+ char buf[40];
+ char osmux_extension[strlen("\nX-Osmux: 255") + 1];
+
+ buf[0] = buf[39] = '\0';
+ ret = sscanf(tok, "%*s %s", buf);
+ if (ret != 1) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Failed to find Endpoint in: %s\n", tok);
+ return;
+ }
+
+ if (osmux_cid >= 0)
+ sprintf(osmux_extension, "\nX-Osmux: %u", osmux_cid & 0xff);
+ else
+ osmux_extension[0] = '\0';
+
+ slen = sprintf((char *) output->l3h, "%s %s %x@mgw MGCP 1.0%s%s",
+ op, buf, endp, osmux_extension, cr ? "\r\n" : "\n");
+ output->l3h = msgb_put(output, slen);
+}
+
+/* we need to replace some strings... */
+struct msgb *bsc_mgcp_rewrite(char *input, int length, int endpoint,
+ const char *ip, int port, int osmux_cid,
+ int *first_payload_type, int ensure_mode_set)
+{
+ static const char crcx_str[] = "CRCX ";
+ static const char dlcx_str[] = "DLCX ";
+ static const char mdcx_str[] = "MDCX ";
+
+ static const char ip_str[] = "c=IN IP4 ";
+ static const char aud_str[] = "m=audio ";
+ static const char fmt_str[] = "a=fmtp:";
+
+ char buf[128];
+ char *running, *token;
+ struct msgb *output;
+
+ /* keep state to add the a=fmtp line */
+ int found_fmtp = 0;
+ int payload = -1;
+ int cr = 1;
+
+ if (length > 4096 - 256) {
+ LOGP(DMGCP, LOGL_ERROR, "Input is too long.\n");
+ return NULL;
+ }
+
+ output = msgb_alloc_headroom(4096, 128, "MGCP rewritten");
+ if (!output) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to allocate new MGCP msg.\n");
+ return NULL;
+ }
+
+ running = input;
+ output->l2h = output->data;
+ output->l3h = output->l2h;
+ for (token = strsep(&running, "\n"); running; token = strsep(&running, "\n")) {
+ int len = strlen(token);
+ cr = len > 0 && token[len - 1] == '\r';
+
+ if (strncmp(crcx_str, token, (sizeof crcx_str) - 1) == 0) {
+ patch_mgcp(output, "CRCX", token, endpoint, len, cr, osmux_cid);
+ } else if (strncmp(dlcx_str, token, (sizeof dlcx_str) - 1) == 0) {
+ patch_mgcp(output, "DLCX", token, endpoint, len, cr, -1);
+ } else if (strncmp(mdcx_str, token, (sizeof mdcx_str) - 1) == 0) {
+ patch_mgcp(output, "MDCX", token, endpoint, len, cr, -1);
+ } else if (strncmp(ip_str, token, (sizeof ip_str) - 1) == 0) {
+ output->l3h = msgb_put(output, strlen(ip_str));
+ memcpy(output->l3h, ip_str, strlen(ip_str));
+ output->l3h = msgb_put(output, strlen(ip));
+ memcpy(output->l3h, ip, strlen(ip));
+
+ if (cr) {
+ output->l3h = msgb_put(output, 2);
+ output->l3h[0] = '\r';
+ output->l3h[1] = '\n';
+ } else {
+ output->l3h = msgb_put(output, 1);
+ output->l3h[0] = '\n';
+ }
+ } else if (strncmp(aud_str, token, (sizeof aud_str) - 1) == 0) {
+ int offset;
+ if (sscanf(token, "m=audio %*d RTP/AVP %n%d", &offset, &payload) != 1) {
+ LOGP(DMGCP, LOGL_ERROR, "Could not parsed audio line.\n");
+ msgb_free(output);
+ return NULL;
+ }
+
+ snprintf(buf, sizeof(buf)-1, "m=audio %d RTP/AVP %s\n",
+ port, &token[offset]);
+ buf[sizeof(buf)-1] = '\0';
+
+ output->l3h = msgb_put(output, strlen(buf));
+ memcpy(output->l3h, buf, strlen(buf));
+ } else if (strncmp(fmt_str, token, (sizeof fmt_str) - 1) == 0) {
+ found_fmtp = 1;
+ goto copy;
+ } else {
+copy:
+ output->l3h = msgb_put(output, len + 1);
+ memcpy(output->l3h, token, len);
+ output->l3h[len] = '\n';
+ }
+ }
+
+ /*
+ * the above code made sure that we have 128 bytes lefts. So we can
+ * safely append another line.
+ */
+ if (ensure_mode_set && !found_fmtp && payload != -1) {
+ snprintf(buf, sizeof(buf) - 1, "a=fmtp:%d mode-set=2%s",
+ payload, cr ? "\r\n" : "\n");
+ buf[sizeof(buf) - 1] = '\0';
+ output->l3h = msgb_put(output, strlen(buf));
+ memcpy(output->l3h, buf, strlen(buf));
+ }
+
+ if (payload != -1 && first_payload_type)
+ *first_payload_type = payload;
+
+ return output;
+}
+
+/*
+ * This comes from the MSC and we will now parse it. The caller needs
+ * to free the msgb.
+ */
+void bsc_nat_handle_mgcp(struct bsc_nat *nat, struct msgb *msg)
+{
+ struct msgb *resp;
+
+ if (!nat->mgcp_ipa) {
+ LOGP(DMGCP, LOGL_ERROR, "MGCP message not allowed on IPA.\n");
+ return;
+ }
+
+ if (msgb_l2len(msg) > sizeof(nat->mgcp_msg) - 1) {
+ LOGP(DMGCP, LOGL_ERROR, "MGCP msg too big for handling.\n");
+ return;
+ }
+
+ memcpy(nat->mgcp_msg, msg->l2h, msgb_l2len(msg));
+ nat->mgcp_length = msgb_l2len(msg);
+ nat->mgcp_msg[nat->mgcp_length] = '\0';
+
+ /* now handle the message */
+ resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+
+ /* we do have a direct answer... e.g. AUEP */
+ if (resp)
+ mgcp_queue_for_call_agent(nat, resp);
+
+ return;
+}
+
+static int mgcp_do_read(struct osmo_fd *fd)
+{
+ struct bsc_nat *nat;
+ struct msgb *msg, *resp;
+ int rc;
+
+ nat = fd->data;
+
+ rc = read(fd->fd, nat->mgcp_msg, sizeof(nat->mgcp_msg) - 1);
+ if (rc <= 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to read errno: %d\n", errno);
+ return -1;
+ }
+
+ nat->mgcp_msg[rc] = '\0';
+ nat->mgcp_length = rc;
+
+ msg = msgb_alloc(sizeof(nat->mgcp_msg), "MGCP GW Read");
+ if (!msg) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to create buffer.\n");
+ return -1;
+ }
+
+ msg->l2h = msgb_put(msg, rc);
+ memcpy(msg->l2h, nat->mgcp_msg, msgb_l2len(msg));
+ resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+ msgb_free(msg);
+
+ /* we do have a direct answer... e.g. AUEP */
+ if (resp)
+ mgcp_queue_for_call_agent(nat, resp);
+
+ return 0;
+}
+
+static int mgcp_do_write(struct osmo_fd *bfd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(bfd->fd, msg->data, msg->len);
+
+ if (rc != msg->len) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to write msg to MGCP CallAgent.\n");
+ return -1;
+ }
+
+ return rc;
+}
+
+static int init_mgcp_socket(struct bsc_nat *nat, struct mgcp_config *cfg)
+{
+ struct sockaddr_in addr;
+ int on;
+
+ cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (cfg->gw_fd.bfd.fd < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to create MGCP socket. errno: %d\n", errno);
+ return -1;
+ }
+
+ on = 1;
+ setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(cfg->source_port);
+ inet_aton(cfg->source_addr, &addr.sin_addr);
+
+ if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to bind on %s:%d errno: %d\n",
+ cfg->source_addr, cfg->source_port, errno);
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ addr.sin_port = htons(2727);
+ inet_aton(cfg->call_agent_addr, &addr.sin_addr);
+ if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n",
+ cfg->call_agent_addr, errno);
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ osmo_wqueue_init(&cfg->gw_fd, 10);
+ cfg->gw_fd.bfd.when = BSC_FD_READ;
+ cfg->gw_fd.bfd.data = nat;
+ cfg->gw_fd.read_cb = mgcp_do_read;
+ cfg->gw_fd.write_cb = mgcp_do_write;
+
+ if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to register MGCP fd.\n");
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+int bsc_mgcp_nat_init(struct bsc_nat *nat)
+{
+ struct mgcp_config *cfg = nat->mgcp_cfg;
+
+ if (!cfg->call_agent_addr) {
+ LOGP(DMGCP, LOGL_ERROR, "The BSC nat requires the call agent ip to be set.\n");
+ return -1;
+ }
+
+ if (cfg->bts_ip) {
+ LOGP(DMGCP, LOGL_ERROR, "Do not set the BTS ip for the nat.\n");
+ return -1;
+ }
+
+ /* initialize the MGCP socket */
+ if (!nat->mgcp_ipa) {
+ int rc = init_mgcp_socket(nat, cfg);
+ if (rc != 0)
+ return rc;
+ }
+
+
+ /* some more MGCP config handling */
+ cfg->data = nat;
+ cfg->policy_cb = bsc_mgcp_policy_cb;
+ cfg->trunk.force_realloc = 1;
+
+ if (cfg->bts_ip)
+ talloc_free(cfg->bts_ip);
+ cfg->bts_ip = "";
+
+ nat->bsc_endpoints = talloc_zero_array(nat,
+ struct bsc_endpoint,
+ cfg->trunk.number_endpoints + 1);
+ if (!nat->bsc_endpoints) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to allocate nat endpoints\n");
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ if (mgcp_reset_transcoder(cfg) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to send packet to the transcoder.\n");
+ talloc_free(nat->bsc_endpoints);
+ nat->bsc_endpoints = NULL;
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc)
+{
+ struct rate_ctr *ctr = NULL;
+ int i;
+
+ if (bsc->cfg)
+ ctr = &bsc->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_CALLS];
+
+ for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+ struct bsc_endpoint *bsc_endp = &bsc->nat->bsc_endpoints[i];
+
+ if (bsc_endp->bsc != bsc)
+ continue;
+
+ if (ctr)
+ rate_ctr_inc(ctr);
+
+ bsc_mgcp_free_endpoint(bsc->nat, i);
+ mgcp_release_endp(&bsc->nat->mgcp_cfg->trunk.endpoints[i]);
+ }
+}