summaryrefslogtreecommitdiff
path: root/src/libosmo-mgcp
diff options
context:
space:
mode:
authorThorsten Alteholz <debian@alteholz.de>2017-12-14 20:07:28 +0100
committerThorsten Alteholz <debian@alteholz.de>2017-12-14 20:07:28 +0100
commit1737d3d0e59c7ffbca33c6b79123d7633185c12a (patch)
treec72aec0dfa2f3c953e999af196ab595e53c084cd /src/libosmo-mgcp
Import Upstream version 1.2.0
Diffstat (limited to 'src/libosmo-mgcp')
-rw-r--r--src/libosmo-mgcp/Makefile.am46
-rw-r--r--src/libosmo-mgcp/g711common.h187
-rw-r--r--src/libosmo-mgcp/mgcp_conn.c287
-rw-r--r--src/libosmo-mgcp/mgcp_ep.c32
-rw-r--r--src/libosmo-mgcp/mgcp_msg.c405
-rw-r--r--src/libosmo-mgcp/mgcp_network.c1264
-rw-r--r--src/libosmo-mgcp/mgcp_osmux.c692
-rw-r--r--src/libosmo-mgcp/mgcp_protocol.c1293
-rw-r--r--src/libosmo-mgcp/mgcp_sdp.c409
-rw-r--r--src/libosmo-mgcp/mgcp_stat.c128
-rw-r--r--src/libosmo-mgcp/mgcp_vty.c1306
11 files changed, 6049 insertions, 0 deletions
diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am
new file mode 100644
index 0000000..fce0e1b
--- /dev/null
+++ b/src/libosmo-mgcp/Makefile.am
@@ -0,0 +1,46 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+# This is not at all related to the release version, but a range of supported
+# API versions. Read TODO_RELEASE in the source tree's root!
+MGCP_LIBVERSION=1:0:0
+
+lib_LTLIBRARIES = \
+ libosmo-mgcp.la \
+ $(NULL)
+
+noinst_HEADERS = \
+ g711common.h \
+ $(NULL)
+
+libosmo_mgcp_la_SOURCES = \
+ mgcp_protocol.c \
+ mgcp_network.c \
+ mgcp_vty.c \
+ mgcp_osmux.c \
+ mgcp_sdp.c \
+ mgcp_msg.c \
+ mgcp_conn.c \
+ mgcp_stat.c \
+ mgcp_ep.c \
+ $(NULL)
+
+libosmo_mgcp_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(MGCP_LIBVERSION)
diff --git a/src/libosmo-mgcp/g711common.h b/src/libosmo-mgcp/g711common.h
new file mode 100644
index 0000000..cb35fc6
--- /dev/null
+++ b/src/libosmo-mgcp/g711common.h
@@ -0,0 +1,187 @@
+/*
+ * PCM - A-Law conversion
+ * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ * Wrapper for linphone Codec class by Simon Morlat <simon.morlat@linphone.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+static inline int val_seg(int val)
+{
+ int r = 0;
+ val >>= 7; /*7 = 4 + 3*/
+ if (val & 0xf0) {
+ val >>= 4;
+ r += 4;
+ }
+ if (val & 0x0c) {
+ val >>= 2;
+ r += 2;
+ }
+ if (val & 0x02)
+ r += 1;
+ return r;
+}
+
+/*
+ * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
+ *
+ * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data.
+ *
+ * Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 0000000wxyza 000wxyz
+ * 0000001wxyza 001wxyz
+ * 000001wxyzab 010wxyz
+ * 00001wxyzabc 011wxyz
+ * 0001wxyzabcd 100wxyz
+ * 001wxyzabcde 101wxyz
+ * 01wxyzabcdef 110wxyz
+ * 1wxyzabcdefg 111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ * G711 is designed for 13 bits input signal, this function add extra shifting to take this into account.
+ */
+
+static inline unsigned char s16_to_alaw(int pcm_val)
+{
+ int mask;
+ int seg;
+ unsigned char aval;
+
+ if (pcm_val >= 0) {
+ mask = 0xD5;
+ } else {
+ mask = 0x55;
+ pcm_val = -pcm_val;
+ if (pcm_val > 0x7fff)
+ pcm_val = 0x7fff;
+ }
+
+ if (pcm_val < 256) /*256 = 32 << 3*/
+ aval = pcm_val >> 4; /*4 = 1 + 3*/
+ else {
+ /* Convert the scaled magnitude to segment number. */
+ seg = val_seg(pcm_val);
+ aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+ }
+ return aval ^ mask;
+}
+
+/*
+ * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM
+ *
+ */
+static inline int alaw_to_s16(unsigned char a_val)
+{
+ int t;
+ int seg;
+
+ a_val ^= 0x55;
+ t = a_val & 0x7f;
+ if (t < 16)
+ t = (t << 4) + 8;
+ else {
+ seg = (t >> 4) & 0x07;
+ t = ((t & 0x0f) << 4) + 0x108;
+ t <<= seg -1;
+ }
+ return ((a_val & 0x80) ? t : -t);
+}
+/*
+ * s16_to_ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ * Biased Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 00000001wxyza 000wxyz
+ * 0000001wxyzab 001wxyz
+ * 000001wxyzabc 010wxyz
+ * 00001wxyzabcd 011wxyz
+ * 0001wxyzabcde 100wxyz
+ * 001wxyzabcdef 101wxyz
+ * 01wxyzabcdefg 110wxyz
+ * 1wxyzabcdefgh 111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz. * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+
+static inline unsigned char s16_to_ulaw(int pcm_val) /* 2's complement (16-bit range) */
+{
+ int mask;
+ int seg;
+ unsigned char uval;
+
+ if (pcm_val < 0) {
+ pcm_val = 0x84 - pcm_val;
+ mask = 0x7f;
+ } else {
+ pcm_val += 0x84;
+ mask = 0xff;
+ }
+ if (pcm_val > 0x7fff)
+ pcm_val = 0x7fff;
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = val_seg(pcm_val);
+
+ /*
+ * Combine the sign, segment, quantization bits;
+ * and complement the code word.
+ */
+ uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+ return uval ^ mask;
+}
+
+/*
+ * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+static inline int ulaw_to_s16(unsigned char u_val)
+{
+ int t;
+
+ /* Complement to obtain normal u-law value. */
+ u_val = ~u_val;
+
+ /*
+ * Extract and bias the quantization bits. Then
+ * shift up by the segment number and subtract out the bias.
+ */
+ t = ((u_val & 0x0f) << 3) + 0x84;
+ t <<= (u_val & 0x70) >> 4;
+
+ return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
+}
diff --git a/src/libosmo-mgcp/mgcp_conn.c b/src/libosmo-mgcp/mgcp_conn.c
new file mode 100644
index 0000000..e0eec63
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_conn.c
@@ -0,0 +1,287 @@
+/* Message connection list handling */
+
+/*
+ * (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/mgcp_conn.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_common.h>
+#include <osmocom/mgcp/mgcp_ep.h>
+
+/* Reset codec state and free memory */
+static void mgcp_rtp_codec_reset(struct mgcp_rtp_codec *codec)
+{
+ codec->payload_type = -1;
+ codec->subtype_name = NULL;
+ codec->audio_name = NULL;
+ codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+ codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+ codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
+ codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS;
+
+ /* see also mgcp_sdp.c, mgcp_set_audio_info() */
+ talloc_free(codec->subtype_name);
+ talloc_free(codec->audio_name);
+}
+
+/* Reset states, free memory, set defaults and reset codec state */
+static void mgcp_rtp_conn_reset(struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_rtp_end *end = &conn->end;
+
+ conn->type = MGCP_RTP_DEFAULT;
+ conn->osmux.allocated_cid = -1;
+
+ end->rtp.fd = -1;
+ end->rtcp.fd = -1;
+ end->local_port = 0;
+ end->packets_rx = 0;
+ end->octets_rx = 0;
+ end->packets_tx = 0;
+ end->octets_tx = 0;
+ end->dropped_packets = 0;
+ end->rtp_port = end->rtcp_port = 0;
+ talloc_free(end->fmtp_extra);
+ end->fmtp_extra = NULL;
+
+ /* Set default values */
+ end->frames_per_packet = 0; /* unknown */
+ end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS;
+ end->output_enabled = 0;
+
+ mgcp_rtp_codec_reset(&end->codec);
+ mgcp_rtp_codec_reset(&end->alt_codec);
+}
+
+/*! allocate a new connection list entry.
+ * \param[in] ctx talloc context
+ * \param[in] endp associated endpoint
+ * \param[in] id identification number of the connection
+ * \param[in] type connection type (e.g. MGCP_CONN_TYPE_RTP)
+ * \returns pointer to allocated connection, NULL on error */
+struct mgcp_conn *mgcp_conn_alloc(void *ctx, struct mgcp_endpoint *endp,
+ uint32_t id, enum mgcp_conn_type type,
+ char *name)
+{
+ struct mgcp_conn *conn;
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL);
+ OSMO_ASSERT(strlen(name) < sizeof(conn->name));
+
+ /* Do not allow more then two connections */
+ if (llist_count(&endp->conns) >= endp->type->max_conns)
+ return NULL;
+
+ /* Prevent duplicate connection IDs */
+ if (mgcp_conn_get(endp, id))
+ return NULL;
+
+ /* Create new connection and add it to the list */
+ conn = talloc_zero(ctx, struct mgcp_conn);
+ if (!conn)
+ return NULL;
+ conn->endp = endp;
+ conn->type = type;
+ conn->mode = MGCP_CONN_NONE;
+ conn->mode_orig = MGCP_CONN_NONE;
+ conn->id = id;
+ conn->u.rtp.conn = conn;
+ strcpy(conn->name, name);
+
+ switch (type) {
+ case MGCP_CONN_TYPE_RTP:
+ mgcp_rtp_conn_reset(&conn->u.rtp);
+ break;
+ default:
+ /* NOTE: This should never be called with an
+ * invalid type, its up to the programmer
+ * to ensure propery types */
+ OSMO_ASSERT(false);
+ }
+
+ llist_add(&conn->entry, &endp->conns);
+
+ return conn;
+}
+
+/*! find a connection by its ID.
+ * \param[in] endp associated endpoint
+ * \param[in] id identification number of the connection
+ * \returns pointer to allocated connection, NULL if not found */
+struct mgcp_conn *mgcp_conn_get(struct mgcp_endpoint *endp, uint32_t id)
+{
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL);
+
+ struct mgcp_conn *conn;
+
+ llist_for_each_entry(conn, &endp->conns, entry) {
+ if (conn->id == id)
+ return conn;
+ }
+
+ return NULL;
+}
+
+/*! find an RTP connection by its ID.
+ * \param[in] endp associated endpoint
+ * \param[in] id identification number of the connection
+ * \returns pointer to allocated connection, NULL if not found */
+struct mgcp_conn_rtp *mgcp_conn_get_rtp(struct mgcp_endpoint *endp, uint32_t id)
+{
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL);
+
+ struct mgcp_conn *conn;
+
+ conn = mgcp_conn_get(endp, id);
+ if (!conn)
+ return NULL;
+
+ if (conn->type == MGCP_CONN_TYPE_RTP)
+ return &conn->u.rtp;
+
+ return NULL;
+}
+
+/*! free a connection by its ID.
+ * \param[in] endp associated endpoint
+ * \param[in] id identification number of the connection */
+void mgcp_conn_free(struct mgcp_endpoint *endp, uint32_t id)
+{
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL);
+
+ struct mgcp_conn *conn;
+
+ conn = mgcp_conn_get(endp, id);
+ if (!conn)
+ return;
+
+ switch (conn->type) {
+ case MGCP_CONN_TYPE_RTP:
+ osmux_disable_conn(&conn->u.rtp);
+ osmux_release_cid(&conn->u.rtp);
+ mgcp_free_rtp_port(&conn->u.rtp.end);
+ break;
+ default:
+ /* NOTE: This should never be called with an
+ * invalid type, its up to the programmer
+ * to ensure propery types */
+ OSMO_ASSERT(false);
+ }
+
+ llist_del(&conn->entry);
+ talloc_free(conn);
+}
+
+/*! free oldest connection in the list.
+ * \param[in] endp associated endpoint */
+void mgcp_conn_free_oldest(struct mgcp_endpoint *endp)
+{
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL);
+
+ struct mgcp_conn *conn;
+
+ if (llist_empty(&endp->conns))
+ return;
+
+ conn = llist_last_entry(&endp->conns, struct mgcp_conn, entry);
+ if (!conn)
+ return;
+
+ mgcp_conn_free(endp, conn->id);
+}
+
+/*! free all connections at once.
+ * \param[in] endp associated endpoint */
+void mgcp_conn_free_all(struct mgcp_endpoint *endp)
+{
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(endp->conns.next != NULL && endp->conns.prev != NULL);
+
+ struct mgcp_conn *conn;
+ struct mgcp_conn *conn_tmp;
+
+ /* Drop all items in the list */
+ llist_for_each_entry_safe(conn, conn_tmp, &endp->conns, entry) {
+ mgcp_conn_free(endp, conn->id);
+ }
+
+ return;
+}
+
+/*! dump basic connection information to human readble string.
+ * \param[in] conn to dump
+ * \returns human readble string */
+char *mgcp_conn_dump(struct mgcp_conn *conn)
+{
+ static char str[256];
+
+ if (!conn) {
+ snprintf(str, sizeof(str), "(null connection)");
+ return str;
+ }
+
+ switch (conn->type) {
+ case MGCP_CONN_TYPE_RTP:
+ /* Dump RTP connection */
+ snprintf(str, sizeof(str), "(%s/rtp, id:%u, ip:%s, "
+ "rtp:%u rtcp:%u)",
+ conn->name,
+ conn->id,
+ inet_ntoa(conn->u.rtp.end.addr),
+ ntohs(conn->u.rtp.end.rtp_port),
+ ntohs(conn->u.rtp.end.rtcp_port));
+ break;
+
+ default:
+ /* Should not happen, we should be able to dump
+ * every possible connection type. */
+ snprintf(str, sizeof(str), "(unknown connection type)");
+ break;
+ }
+
+ return str;
+}
+
+/*! find destination connection on a specific endpoint.
+ * \param[in] conn to search a destination for
+ * \returns destination connection, NULL on failure */
+struct mgcp_conn *mgcp_find_dst_conn(struct mgcp_conn *conn)
+{
+ struct mgcp_endpoint *endp;
+ struct mgcp_conn *partner_conn;
+ endp = conn->endp;
+
+ /*! NOTE: This simply works by grabbing the first connection that is
+ * not the supplied connection, which is suitable for endpoints that
+ * do not serve more than two connections. */
+
+ llist_for_each_entry(partner_conn, &endp->conns, entry) {
+ if (conn != partner_conn) {
+ return partner_conn;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/libosmo-mgcp/mgcp_ep.c b/src/libosmo-mgcp/mgcp_ep.c
new file mode 100644
index 0000000..72ca691
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_ep.c
@@ -0,0 +1,32 @@
+/* Endpoint types */
+
+/*
+ * (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/mgcp_ep.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+
+/* Endpoint typeset definition */
+const struct mgcp_endpoint_typeset ep_typeset = {
+ /* Specify endpoint properties for RTP endpoint */
+ .rtp.max_conns = 2,
+ .rtp.dispatch_rtp_cb = mgcp_dispatch_rtp_bridge_cb
+};
diff --git a/src/libosmo-mgcp/mgcp_msg.c b/src/libosmo-mgcp/mgcp_msg.c
new file mode 100644
index 0000000..d686bca
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_msg.c
@@ -0,0 +1,405 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* Message parser/generator utilities */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <limits.h>
+
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_common.h>
+#include <osmocom/mgcp/mgcp_msg.h>
+#include <osmocom/mgcp/mgcp_conn.h>
+
+/*! Display an mgcp message on the log output.
+ * \param[in] message mgcp message string
+ * \param[in] len message mgcp message string length
+ * \param[in] preamble string to display in logtext in front of each line */
+void mgcp_disp_msg(unsigned char *message, unsigned int len, char *preamble)
+{
+ unsigned char line[80];
+ unsigned char *ptr;
+ unsigned int consumed = 0;
+ unsigned int consumed_line = 0;
+ unsigned int line_count = 0;
+
+ if (!log_check_level(DLMGCP, LOGL_DEBUG))
+ return;
+
+ while (1) {
+ memset(line, 0, sizeof(line));
+ ptr = line;
+ consumed_line = 0;
+ do {
+ if (*message != '\n' && *message != '\r') {
+ *ptr = *message;
+ ptr++;
+ }
+ message++;
+ consumed++;
+ consumed_line++;
+ } while (*message != '\n' && consumed < len
+ && consumed_line < sizeof(line));
+
+ if (strlen((const char *)line)) {
+ LOGP(DLMGCP, LOGL_DEBUG, "%s: line #%02u: %s\n",
+ preamble, line_count, line);
+ line_count++;
+ }
+
+ if (consumed >= len)
+ return;
+ }
+}
+
+/*! Parse connection mode.
+ * \param[in] mode as string (recvonly, sendrecv, sendonly or loopback)
+ * \param[in] endp pointer to endpoint (only used for log output)
+ * \param[out] associated connection to be modified accordingly
+ * \returns 0 on success, -1 on error */
+int mgcp_parse_conn_mode(const char *mode, struct mgcp_endpoint *endp,
+ struct mgcp_conn *conn)
+{
+ int ret = 0;
+
+ if (!mode) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "endpoint:%x missing connection mode\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+ if (!conn)
+ return -1;
+ if (!endp)
+ return -1;
+
+ if (strcmp(mode, "recvonly") == 0)
+ conn->mode = MGCP_CONN_RECV_ONLY;
+ else if (strcmp(mode, "sendrecv") == 0)
+ conn->mode = MGCP_CONN_RECV_SEND;
+ else if (strcmp(mode, "sendonly") == 0)
+ conn->mode = MGCP_CONN_SEND_ONLY;
+ else if (strcmp(mode, "loopback") == 0)
+ conn->mode = MGCP_CONN_LOOPBACK;
+ else {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "endpoint:%x unknown connection mode: '%s'\n",
+ ENDPOINT_NUMBER(endp), mode);
+ ret = -1;
+ }
+
+ /* Special handling für RTP connections */
+ if (conn->type == MGCP_CONN_TYPE_RTP) {
+ conn->u.rtp.end.output_enabled =
+ conn->mode & MGCP_CONN_SEND_ONLY ? 1 : 0;
+ }
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint:%x conn:%s\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn));
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint:%x connection mode '%s' %d\n",
+ ENDPOINT_NUMBER(endp), mode, conn->mode);
+
+ /* Special handling für RTP connections */
+ if (conn->type == MGCP_CONN_TYPE_RTP) {
+ LOGP(DLMGCP, LOGL_DEBUG, "endpoint:%x output_enabled %d\n",
+ ENDPOINT_NUMBER(endp), conn->u.rtp.end.output_enabled);
+ }
+
+ /* The VTY might change the connection mode at any time, so we have
+ * to hold a copy of the original connection mode */
+ conn->mode_orig = conn->mode;
+
+ return ret;
+}
+
+/* We have a null terminated string with the endpoint name here. We only
+ * support two kinds. Simple ones as seen on the BSC level and the ones
+ * seen on the trunk side. (helper function for find_endpoint()) */
+static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg,
+ const char *mgcp)
+{
+ char *rest = NULL;
+ struct mgcp_trunk_config *tcfg;
+ int trunk, endp;
+
+ trunk = strtoul(mgcp + 6, &rest, 10);
+ if (rest == NULL || rest[0] != '/' || trunk < 1) {
+ LOGP(DLMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp);
+ return NULL;
+ }
+
+ endp = strtoul(rest + 1, &rest, 10);
+ if (rest == NULL || rest[0] != '@') {
+ LOGP(DLMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp);
+ return NULL;
+ }
+
+ /* signalling is on timeslot 1 */
+ if (endp == 1)
+ return NULL;
+
+ tcfg = mgcp_trunk_num(cfg, trunk);
+ if (!tcfg) {
+ LOGP(DLMGCP, LOGL_ERROR, "The trunk %d is not declared.\n",
+ trunk);
+ return NULL;
+ }
+
+ if (!tcfg->endpoints) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Endpoints of trunk %d not allocated.\n", trunk);
+ return NULL;
+ }
+
+ if (endp < 1 || endp >= tcfg->number_endpoints) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n",
+ mgcp);
+ return NULL;
+ }
+
+ return &tcfg->endpoints[endp];
+}
+
+/* Search the endpoint pool for the endpoint that had been selected via the
+ * MGCP message (helper function for mgcp_analyze_header()) */
+static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg,
+ const char *mgcp)
+{
+ char *endptr = NULL;
+ unsigned int gw = INT_MAX;
+
+ if (strncmp(mgcp, "ds/e1", 5) == 0)
+ return find_e1_endpoint(cfg, mgcp);
+
+ gw = strtoul(mgcp, &endptr, 16);
+ if (gw > 0 && gw < cfg->trunk.number_endpoints && endptr[0] == '@')
+ return &cfg->trunk.endpoints[gw];
+
+ LOGP(DLMGCP, LOGL_ERROR, "Not able to find the endpoint: '%s'\n", mgcp);
+ return NULL;
+}
+
+/*! Analyze and parse the the hader of an MGCP messeage string.
+ * \param[out] pdata caller provided memory to store the parsing results
+ * \param[in] data mgcp message string
+ * \returns when the status line was complete and transaction_id and
+ * endp out parameters are set, -1 on error */
+int mgcp_parse_header(struct mgcp_parse_data *pdata, char *data)
+{
+ int i = 0;
+ char *elem, *save = NULL;
+
+ /*! This function will parse the header part of the received
+ * MGCP message. The parsing results are stored in pdata.
+ * The function will also automatically search the pool with
+ * available endpoints in order to find an endpoint that matches
+ * the endpoint string in in the header */
+
+ OSMO_ASSERT(data);
+ pdata->trans = "000000";
+
+ for (elem = strtok_r(data, " ", &save); elem;
+ elem = strtok_r(NULL, " ", &save)) {
+ switch (i) {
+ case 0:
+ pdata->trans = elem;
+ break;
+ case 1:
+ pdata->endp = find_endpoint(pdata->cfg, elem);
+ if (!pdata->endp) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Unable to find Endpoint `%s'\n", elem);
+ return -1;
+ }
+ break;
+ case 2:
+ if (strcmp("MGCP", elem)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "MGCP header parsing error\n");
+ return -1;
+ }
+ break;
+ case 3:
+ if (strcmp("1.0", elem)) {
+ LOGP(DLMGCP, LOGL_ERROR, "MGCP version `%s' "
+ "not supported\n", elem);
+ return -1;
+ }
+ break;
+ }
+ i++;
+ }
+
+ if (i != 4) {
+ LOGP(DLMGCP, LOGL_ERROR, "MGCP status line too short.\n");
+ pdata->trans = "000000";
+ pdata->endp = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*! Extract OSMUX CID from an MGCP parameter line (string).
+ * \param[in] line single parameter line from the MGCP message
+ * \returns OSMUX CID, -1 on error */
+int mgcp_parse_osmux_cid(const char *line)
+{
+ int osmux_cid;
+
+ if (sscanf(line + 2, "Osmux: %u", &osmux_cid) != 1)
+ return -1;
+
+ if (osmux_cid > OSMUX_CID_MAX) {
+ LOGP(DLMGCP, LOGL_ERROR, "Osmux ID too large: %u > %u\n",
+ osmux_cid, OSMUX_CID_MAX);
+ return -1;
+ }
+ LOGP(DLMGCP, LOGL_DEBUG, "bsc-nat offered Osmux CID %u\n", osmux_cid);
+
+ return osmux_cid;
+}
+
+/*! Check MGCP parameter line (string) for plausibility.
+ * \param[in] endp pointer to endpoint (only used for log output)
+ * \param[in] line single parameter line from the MGCP message
+ * \returns 1 when line seems plausible, 0 on error */
+int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line)
+{
+ const size_t line_len = strlen(line);
+ if (line[0] != '\0' && line_len < 2) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Wrong MGCP option format: '%s' on 0x%x\n",
+ line, ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ /* FIXME: A couple more checks wouldn't hurt... */
+
+ return 1;
+}
+
+/*! Check if the specified callid seems plausible.
+ * \param[in] endp pointer to endpoint
+ * \param{in] callid to verify
+ * \returns 1 when callid seems plausible, 0 on error */
+int mgcp_verify_call_id(struct mgcp_endpoint *endp, const char *callid)
+{
+ /*! This function compares the supplied callid with the called that is
+ * stored in the endpoint structure. */
+
+ if (!endp)
+ return -1;
+ if (!callid)
+ return -1;
+ if (!endp->callid)
+ return -1;
+
+ if (strcmp(endp->callid, callid) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "endpoint:%x CallIDs does not match '%s' != '%s'\n",
+ ENDPOINT_NUMBER(endp), endp->callid, callid);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*! Check if the specified connection id seems plausible.
+ * \param[in] endp pointer to endpoint
+ * \param{in] connection id to verify
+ * \returns 1 when connection id seems plausible, 0 on error */
+int mgcp_verify_ci(struct mgcp_endpoint *endp, const char *ci)
+{
+ uint32_t id;
+
+ if (!endp)
+ return -1;
+
+ id = strtoul(ci, NULL, 10);
+
+ if (mgcp_conn_get(endp, id))
+ return 0;
+
+ LOGP(DLMGCP, LOGL_ERROR,
+ "endpoint:%x No connection found under ConnectionIdentifier %u\n",
+ ENDPOINT_NUMBER(endp), id);
+
+ return -1;
+}
+
+/*! Extract individual lines from MCGP message.
+ * \param[in] str MGCP message string, consisting of multiple lines
+ * \param{in] saveptr pointer to next line in str
+ * \returns line, NULL when done */
+char *mgcp_strline(char *str, char **saveptr)
+{
+ char *result;
+
+ /*! The function must be called with *str set to the input string
+ * for the first line. After that saveptr will be initalized.
+ * all consecutive lines are extracted by calling the function
+ * with str set to NULL. When done, the function will return NULL
+ * to indicate that all lines have been parsed. */
+
+ if (str)
+ *saveptr = str;
+
+ result = *saveptr;
+
+ if (*saveptr != NULL) {
+ *saveptr = strpbrk(*saveptr, "\r\n");
+
+ if (*saveptr != NULL) {
+ char *eos = *saveptr;
+
+ if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n')
+ (*saveptr)++;
+ (*saveptr)++;
+ if ((*saveptr)[0] == '\0')
+ *saveptr = NULL;
+
+ *eos = '\0';
+ }
+ }
+
+ return result;
+}
+
+/*! Parse CI from a given string.
+ * \param[out] caller provided memory to store the result
+ * \param{in] string containing the connection id
+ * \returns 0 on success, -1 on error */
+int mgcp_parse_ci(uint32_t *conn_id, const char *ci)
+{
+
+ OSMO_ASSERT(conn_id);
+
+ if (!ci)
+ return -1;
+
+ *conn_id = strtoul(ci, NULL, 10);
+
+ return 0;
+}
diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c
new file mode 100644
index 0000000..d51b829
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_network.c
@@ -0,0 +1,1264 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/netif/rtp.h>
+#include <osmocom/mgcp/mgcp.h>
+#include <osmocom/mgcp/mgcp_common.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_stat.h>
+#include <osmocom/mgcp/osmux.h>
+#include <osmocom/mgcp/mgcp_conn.h>
+#include <osmocom/mgcp/mgcp_ep.h>
+#include <osmocom/mgcp/debug.h>
+
+#define RTP_SEQ_MOD (1 << 16)
+#define RTP_MAX_DROPOUT 3000
+#define RTP_MAX_MISORDER 100
+#define RTP_BUF_SIZE 4096
+
+enum {
+ MGCP_PROTO_RTP,
+ MGCP_PROTO_RTCP,
+};
+
+/*! Determine the local rtp bind IP-address.
+ * \param[out] addr caller provided memory to store the resulting IP-Address
+ * \param[in] endp mgcp endpoint, that holds a copy of the VTY parameters
+ *
+ * The local bind IP-address is automatically selected by probing the
+ * IP-Address of the interface that is pointing towards the remote IP-Address,
+ * if no remote IP-Address is known yet, the statically configured
+ * IP-Addresses are used as fallback. */
+void mgcp_get_local_addr(char *addr, struct mgcp_conn_rtp *conn)
+{
+
+ struct mgcp_endpoint *endp;
+ int rc;
+ endp = conn->conn->endp;
+
+ /* Try probing the local IP-Address */
+ if (endp->cfg->net_ports.bind_addr_probe && conn->end.addr.s_addr != 0) {
+ rc = osmo_sock_local_ip(addr, inet_ntoa(conn->end.addr));
+ if (rc < 0)
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x CI:%i local interface auto detection failed, using configured addresses...\n",
+ ENDPOINT_NUMBER(endp), conn->conn->id);
+ else {
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x CI:%i selected local rtp bind ip %s by probing using remote ip %s\n",
+ ENDPOINT_NUMBER(endp), conn->conn->id, addr,
+ inet_ntoa(conn->end.addr));
+ return;
+ }
+ }
+
+ /* Select from preconfigured IP-Addresses */
+ if (endp->cfg->net_ports.bind_addr) {
+ /* Check there is a bind IP for the RTP traffic configured,
+ * if so, use that IP-Address */
+ strncpy(addr, endp->cfg->net_ports.bind_addr, INET_ADDRSTRLEN);
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x CI:%i using configured rtp bind ip as local bind ip %s\n",
+ ENDPOINT_NUMBER(endp), conn->conn->id, addr);
+ } else {
+ /* No specific bind IP is configured for the RTP traffic, so
+ * assume the IP where we listen for incoming MGCP messages
+ * as bind IP */
+ strncpy(addr, endp->cfg->source_addr, INET_ADDRSTRLEN);
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x CI:%i using mgcp bind ip as local rtp bind ip: %s\n",
+ ENDPOINT_NUMBER(endp), conn->conn->id, addr);
+ }
+}
+
+/* This does not need to be a precision timestamp and
+ * is allowed to wrap quite fast. The returned value is
+ * 1/codec_rate seconds. */
+static uint32_t get_current_ts(unsigned codec_rate)
+{
+ struct timespec tp;
+ uint64_t ret;
+
+ if (!codec_rate)
+ return 0;
+
+ memset(&tp, 0, sizeof(tp));
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
+ LOGP(DRTP, LOGL_NOTICE, "Getting the clock failed.\n");
+
+ /* convert it to 1/unit seconds */
+ ret = tp.tv_sec;
+ ret *= codec_rate;
+ ret += (int64_t) tp.tv_nsec * codec_rate / 1000 / 1000 / 1000;
+
+ return ret;
+}
+
+/*! send udp packet.
+ * \param[in] fd associated file descriptor
+ * \param[in] addr destination ip-address
+ * \param[in] port destination UDP port
+ * \param[in] buf buffer that holds the data to be send
+ * \param[in] len length of the data to be sent
+ * \returns bytes sent, -1 on error */
+int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
+{
+ struct sockaddr_in out;
+
+ LOGP(DRTP, LOGL_DEBUG,
+ "sending %i bytes length packet to %s:%u ...\n",
+ len, inet_ntoa(*addr), ntohs(port));
+
+ out.sin_family = AF_INET;
+ out.sin_port = port;
+ memcpy(&out.sin_addr, addr, sizeof(*addr));
+
+ return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out));
+}
+
+/*! send RTP dummy packet (to keep NAT connection open).
+ * \param[in] endp mcgp endpoint that holds the RTP connection
+ * \param[in] conn associated RTP connection
+ * \returns bytes sent, -1 on error */
+int mgcp_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn)
+{
+ static char buf[] = { MGCP_DUMMY_LOAD };
+ int rc;
+ int was_rtcp = 0;
+
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(conn);
+
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x sending dummy packet...\n", ENDPOINT_NUMBER(endp));
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x conn:%s\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn));
+
+ rc = mgcp_udp_send(conn->end.rtp.fd, &conn->end.addr,
+ conn->end.rtp_port, buf, 1);
+
+ if (rc == -1)
+ goto failed;
+
+ if (endp->tcfg->omit_rtcp)
+ return rc;
+
+ was_rtcp = 1;
+ rc = mgcp_udp_send(conn->end.rtcp.fd, &conn->end.addr,
+ conn->end.rtcp_port, buf, 1);
+
+ if (rc >= 0)
+ return rc;
+
+failed:
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x Failed to send dummy %s packet.\n",
+ ENDPOINT_NUMBER(endp), was_rtcp ? "RTCP" : "RTP");
+
+ return -1;
+}
+
+/* Compute timestamp alignment error */
+static int32_t ts_alignment_error(struct mgcp_rtp_stream_state *sstate,
+ int ptime, uint32_t timestamp)
+{
+ int32_t timestamp_delta;
+
+ if (ptime == 0)
+ return 0;
+
+ /* Align according to: T - Tlast = k * Tptime */
+ timestamp_delta = timestamp - sstate->last_timestamp;
+
+ return timestamp_delta % ptime;
+}
+
+/* Check timestamp and sequence number for plausibility */
+static int check_rtp_timestamp(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_state *state,
+ struct mgcp_rtp_stream_state *sstate,
+ struct mgcp_rtp_end *rtp_end,
+ struct sockaddr_in *addr,
+ uint16_t seq, uint32_t timestamp,
+ const char *text, int32_t * tsdelta_out)
+{
+ int32_t tsdelta;
+ int32_t timestamp_error;
+
+ /* Not fully intialized, skip */
+ if (sstate->last_tsdelta == 0 && timestamp == sstate->last_timestamp)
+ return 0;
+
+ if (seq == sstate->last_seq) {
+ if (timestamp != sstate->last_timestamp) {
+ sstate->err_ts_counter += 1;
+ LOGP(DRTP, LOGL_ERROR,
+ "The %s timestamp delta is != 0 but the sequence "
+ "number %d is the same, "
+ "TS offset: %d, SeqNo offset: %d "
+ "on 0x%x SSRC: %u timestamp: %u "
+ "from %s:%d\n",
+ text, seq,
+ state->timestamp_offset, state->seq_offset,
+ ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ }
+ return 0;
+ }
+
+ tsdelta =
+ (int32_t)(timestamp - sstate->last_timestamp) /
+ (int16_t)(seq - sstate->last_seq);
+
+ if (tsdelta == 0) {
+ /* Don't update *tsdelta_out */
+ LOGP(DRTP, LOGL_NOTICE,
+ "The %s timestamp delta is %d "
+ "on 0x%x SSRC: %u timestamp: %u "
+ "from %s:%d\n",
+ text, tsdelta,
+ ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+
+ return 0;
+ }
+
+ if (sstate->last_tsdelta != tsdelta) {
+ if (sstate->last_tsdelta) {
+ LOGP(DRTP, LOGL_INFO,
+ "The %s timestamp delta changes from %d to %d "
+ "on 0x%x SSRC: %u timestamp: %u from %s:%d\n",
+ text, sstate->last_tsdelta, tsdelta,
+ ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ }
+ }
+
+ if (tsdelta_out)
+ *tsdelta_out = tsdelta;
+
+ timestamp_error =
+ ts_alignment_error(sstate, state->packet_duration, timestamp);
+
+ if (timestamp_error) {
+ sstate->err_ts_counter += 1;
+ LOGP(DRTP, LOGL_NOTICE,
+ "The %s timestamp has an alignment error of %d "
+ "on 0x%x SSRC: %u "
+ "SeqNo delta: %d, TS delta: %d, dTS/dSeq: %d "
+ "from %s:%d. ptime: %d\n",
+ text, timestamp_error,
+ ENDPOINT_NUMBER(endp), sstate->ssrc,
+ (int16_t)(seq - sstate->last_seq),
+ (int32_t)(timestamp - sstate->last_timestamp),
+ tsdelta,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ state->packet_duration);
+ }
+ return 1;
+}
+
+/* Set the timestamp offset according to the packet duration. */
+static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_state *state,
+ struct mgcp_rtp_end *rtp_end,
+ struct sockaddr_in *addr,
+ int16_t delta_seq, uint32_t in_timestamp)
+{
+ int32_t tsdelta = state->packet_duration;
+ int timestamp_offset;
+ uint32_t out_timestamp;
+
+ if (tsdelta == 0) {
+ tsdelta = state->out_stream.last_tsdelta;
+ if (tsdelta != 0) {
+ LOGP(DRTP, LOGL_NOTICE,
+ "A fixed packet duration is not available on 0x%x, "
+ "using last output timestamp delta instead: %d "
+ "from %s:%d\n",
+ ENDPOINT_NUMBER(endp), tsdelta,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ } else {
+ tsdelta = rtp_end->codec.rate * 20 / 1000;
+ LOGP(DRTP, LOGL_NOTICE,
+ "Fixed packet duration and last timestamp delta "
+ "are not available on 0x%x, "
+ "using fixed 20ms instead: %d "
+ "from %s:%d\n",
+ ENDPOINT_NUMBER(endp), tsdelta,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ }
+ }
+
+ out_timestamp = state->out_stream.last_timestamp + delta_seq * tsdelta;
+ timestamp_offset = out_timestamp - in_timestamp;
+
+ if (state->timestamp_offset != timestamp_offset) {
+ state->timestamp_offset = timestamp_offset;
+
+ LOGP(DRTP, LOGL_NOTICE,
+ "Timestamp offset change on 0x%x SSRC: %u "
+ "SeqNo delta: %d, TS offset: %d, "
+ "from %s:%d\n",
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ delta_seq, state->timestamp_offset,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ }
+
+ return timestamp_offset;
+}
+
+/* Set the timestamp offset according to the packet duration. */
+static int align_rtp_timestamp_offset(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_state *state,
+ struct mgcp_rtp_end *rtp_end,
+ struct sockaddr_in *addr,
+ uint32_t timestamp)
+{
+ int ts_error = 0;
+ int ts_check = 0;
+ int ptime = state->packet_duration;
+
+ /* Align according to: T + Toffs - Tlast = k * Tptime */
+
+ ts_error = ts_alignment_error(&state->out_stream, ptime,
+ timestamp + state->timestamp_offset);
+
+ /* If there is an alignment error, we have to compensate it */
+ if (ts_error) {
+ state->timestamp_offset += ptime - ts_error;
+
+ LOGP(DRTP, LOGL_NOTICE,
+ "Corrected timestamp alignment error of %d on 0x%x SSRC: %u "
+ "new TS offset: %d, "
+ "from %s:%d\n",
+ ts_error,
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ state->timestamp_offset, inet_ntoa(addr->sin_addr),
+ ntohs(addr->sin_port));
+ }
+
+ /* Check we really managed to compensate the timestamp
+ * offset. There should not be any remaining error, failing
+ * here would point to a serous problem with the alingnment
+ * error computation fuction */
+ ts_check = ts_alignment_error(&state->out_stream, ptime,
+ timestamp + state->timestamp_offset);
+ OSMO_ASSERT(ts_check == 0);
+
+ /* Return alignment error before compensation */
+ return ts_error;
+}
+
+/*! dummy callback to disable transcoding (see also cfg->rtp_processing_cb).
+ * \param[in] associated endpoint
+ * \param[in] destination RTP end
+ * \param[in,out] pointer to buffer with voice data
+ * \param[in] voice data length
+ * \param[in] maxmimum size of caller provided voice data buffer
+ * \returns ignores input parameters, return always 0 */
+int mgcp_rtp_processing_default(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end,
+ char *data, int *len, int buf_size)
+{
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x transcoding disabled\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+}
+
+/*! dummy callback to disable transcoding (see also cfg->setup_rtp_processing_cb).
+ * \param[in] associated endpoint
+ * \param[in] destination RTP end
+ * \param[in] source RTP end
+ * \returns ignores input parameters, return always 0 */
+int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end,
+ struct mgcp_rtp_end *src_end)
+{
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x transcoding disabled\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+}
+
+void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
+ int *payload_type,
+ const char **audio_name,
+ const char **fmtp_extra,
+ struct mgcp_conn_rtp *conn)
+{
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x conn:%s using format defaults\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn));
+
+ *payload_type = conn->end.codec.payload_type;
+ *audio_name = conn->end.codec.audio_name;
+ *fmtp_extra = conn->end.fmtp_extra;
+}
+
+void mgcp_rtp_annex_count(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_state *state, const uint16_t seq,
+ const int32_t transit, const uint32_t ssrc)
+{
+ int32_t d;
+
+ /* initialize or re-initialize */
+ if (!state->stats_initialized || state->stats_ssrc != ssrc) {
+ state->stats_initialized = 1;
+ state->stats_base_seq = seq;
+ state->stats_max_seq = seq - 1;
+ state->stats_ssrc = ssrc;
+ state->stats_jitter = 0;
+ state->stats_transit = transit;
+ state->stats_cycles = 0;
+ } else {
+ uint16_t udelta;
+
+ /* The below takes the shape of the validation of
+ * Appendix A. Check if there is something weird with
+ * the sequence number, otherwise check for a wrap
+ * around in the sequence number.
+ * It can't wrap during the initialization so let's
+ * skip it here. The Appendix A probably doesn't have
+ * this issue because of the probation. */
+ udelta = seq - state->stats_max_seq;
+ if (udelta < RTP_MAX_DROPOUT) {
+ if (seq < state->stats_max_seq)
+ state->stats_cycles += RTP_SEQ_MOD;
+ } else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) {
+ LOGP(DRTP, LOGL_NOTICE,
+ "RTP seqno made a very large jump on 0x%x delta: %u\n",
+ ENDPOINT_NUMBER(endp), udelta);
+ }
+ }
+
+ /* Calculate the jitter between the two packages. The TS should be
+ * taken closer to the read function. This was taken from the
+ * Appendix A of RFC 3550. Timestamp and arrival_time have a 1/rate
+ * resolution. */
+ d = transit - state->stats_transit;
+ state->stats_transit = transit;
+ if (d < 0)
+ d = -d;
+ state->stats_jitter += d - ((state->stats_jitter + 8) >> 4);
+ state->stats_max_seq = seq;
+}
+
+/* The RFC 3550 Appendix A assumes there are multiple sources but
+ * some of the supported endpoints (e.g. the nanoBTS) can only handle
+ * one source and this code will patch RTP header to appear as if there
+ * is only one source.
+ * There is also no probation period for new sources. Every RTP header
+ * we receive will be seen as a switch in streams. */
+void mgcp_patch_and_count(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_state *state,
+ struct mgcp_rtp_end *rtp_end,
+ struct sockaddr_in *addr, char *data, int len)
+{
+ uint32_t arrival_time;
+ int32_t transit;
+ uint16_t seq;
+ uint32_t timestamp, ssrc;
+ struct rtp_hdr *rtp_hdr;
+ int payload = rtp_end->codec.payload_type;
+
+ if (len < sizeof(*rtp_hdr))
+ return;
+
+ rtp_hdr = (struct rtp_hdr *)data;
+ seq = ntohs(rtp_hdr->sequence);
+ timestamp = ntohl(rtp_hdr->timestamp);
+ arrival_time = get_current_ts(rtp_end->codec.rate);
+ ssrc = ntohl(rtp_hdr->ssrc);
+ transit = arrival_time - timestamp;
+
+ mgcp_rtp_annex_count(endp, state, seq, transit, ssrc);
+
+ if (!state->initialized) {
+ state->initialized = 1;
+ state->in_stream.last_seq = seq - 1;
+ state->in_stream.ssrc = state->orig_ssrc = ssrc;
+ state->in_stream.last_tsdelta = 0;
+ state->packet_duration =
+ mgcp_rtp_packet_duration(endp, rtp_end);
+ state->out_stream = state->in_stream;
+ state->out_stream.last_timestamp = timestamp;
+ state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */
+ LOGP(DRTP, LOGL_INFO,
+ "endpoint:%x initializing stream, SSRC: %u timestamp: %u "
+ "pkt-duration: %d, from %s:%d\n",
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ state->seq_offset, state->packet_duration,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ if (state->packet_duration == 0) {
+ state->packet_duration =
+ rtp_end->codec.rate * 20 / 1000;
+ LOGP(DRTP, LOGL_NOTICE,
+ "endpoint:%x fixed packet duration is not available, "
+ "using fixed 20ms instead: %d from %s:%d\n",
+ ENDPOINT_NUMBER(endp), state->packet_duration,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ }
+ } else if (state->in_stream.ssrc != ssrc) {
+ LOGP(DRTP, LOGL_NOTICE,
+ "endpoint:%x SSRC changed: %u -> %u "
+ "from %s:%d\n",
+ ENDPOINT_NUMBER(endp),
+ state->in_stream.ssrc, rtp_hdr->ssrc,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+
+ state->in_stream.ssrc = ssrc;
+ if (rtp_end->force_constant_ssrc) {
+ int16_t delta_seq;
+
+ /* Always increment seqno by 1 */
+ state->seq_offset =
+ (state->out_stream.last_seq + 1) - seq;
+
+ /* Estimate number of packets that would have been sent */
+ delta_seq =
+ (arrival_time - state->in_stream.last_arrival_time
+ + state->packet_duration / 2) /
+ state->packet_duration;
+
+ adjust_rtp_timestamp_offset(endp, state, rtp_end, addr,
+ delta_seq, timestamp);
+
+ state->patch_ssrc = 1;
+ ssrc = state->orig_ssrc;
+ if (rtp_end->force_constant_ssrc != -1)
+ rtp_end->force_constant_ssrc -= 1;
+
+ LOGP(DRTP, LOGL_NOTICE,
+ "endpoint:%x SSRC patching enabled, SSRC: %u "
+ "SeqNo offset: %d, TS offset: %d "
+ "from %s:%d\n",
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ state->seq_offset, state->timestamp_offset,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+ }
+
+ state->in_stream.last_tsdelta = 0;
+ } else {
+ /* Compute current per-packet timestamp delta */
+ check_rtp_timestamp(endp, state, &state->in_stream, rtp_end,
+ addr, seq, timestamp, "input",
+ &state->in_stream.last_tsdelta);
+
+ if (state->patch_ssrc)
+ ssrc = state->orig_ssrc;
+ }
+
+ /* Save before patching */
+ state->in_stream.last_timestamp = timestamp;
+ state->in_stream.last_seq = seq;
+ state->in_stream.last_arrival_time = arrival_time;
+
+ if (rtp_end->force_aligned_timing &&
+ state->out_stream.ssrc == ssrc && state->packet_duration)
+ /* Align the timestamp offset */
+ align_rtp_timestamp_offset(endp, state, rtp_end, addr,
+ timestamp);
+
+ /* Store the updated SSRC back to the packet */
+ if (state->patch_ssrc)
+ rtp_hdr->ssrc = htonl(ssrc);
+
+ /* Apply the offset and store it back to the packet.
+ * This won't change anything if the offset is 0, so the conditional is
+ * omitted. */
+ seq += state->seq_offset;
+ rtp_hdr->sequence = htons(seq);
+ timestamp += state->timestamp_offset;
+ rtp_hdr->timestamp = htonl(timestamp);
+
+ /* Check again, whether the timestamps are still valid */
+ if (state->out_stream.ssrc == ssrc)
+ check_rtp_timestamp(endp, state, &state->out_stream, rtp_end,
+ addr, seq, timestamp, "output",
+ &state->out_stream.last_tsdelta);
+
+ /* Save output values */
+ state->out_stream.last_seq = seq;
+ state->out_stream.last_timestamp = timestamp;
+ state->out_stream.ssrc = ssrc;
+
+ if (payload < 0)
+ return;
+
+#if 0
+ DEBUGP(DRTP,
+ "endpoint:%x payload hdr payload %u -> endp payload %u\n",
+ ENDPOINT_NUMBER(endp), rtp_hdr->payload_type, payload);
+ rtp_hdr->payload_type = payload;
+#endif
+}
+
+/* Forward data to a debug tap. This is debug function that is intended for
+ * debugging the voice traffic with tools like gstreamer */
+static void forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf,
+ int len)
+{
+ int rc;
+
+ if (!tap->enabled)
+ return;
+
+ rc = sendto(fd, buf, len, 0, (struct sockaddr *)&tap->forward,
+ sizeof(tap->forward));
+
+ if (rc < 0)
+ LOGP(DRTP, LOGL_ERROR,
+ "Forwarding tapped (debug) voice data failed.\n");
+}
+
+/*! Send RTP/RTCP data to a specified destination connection.
+ * \param[in] endp associated endpoint (for configuration, logging)
+ * \param[in] is_rtp flag to specify if the packet is of type RTP or RTCP
+ * \param[in] spoofed source address (set to NULL to disable)
+ * \param[in] buf buffer that contains the RTP/RTCP data
+ * \param[in] len length of the buffer that contains the RTP/RTCP data
+ * \param[in] conn_src associated source connection
+ * \param[in] conn_dst associated destination connection
+ * \returns 0 on success, -1 on ERROR */
+int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
+ char *buf, int len, struct mgcp_conn_rtp *conn_src,
+ struct mgcp_conn_rtp *conn_dst)
+{
+ /*! When no destination connection is available (e.g. when only one
+ * connection in loopback mode exists), then the source connection
+ * shall be specified as destination connection */
+
+ struct mgcp_trunk_config *tcfg = endp->tcfg;
+ struct mgcp_rtp_end *rtp_end;
+ struct mgcp_rtp_state *rtp_state;
+ char *dest_name;
+
+ OSMO_ASSERT(conn_src);
+ OSMO_ASSERT(conn_dst);
+
+ if (is_rtp) {
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x delivering RTP packet...\n",
+ ENDPOINT_NUMBER(endp));
+ } else {
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x delivering RTCP packet...\n",
+ ENDPOINT_NUMBER(endp));
+ }
+
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x loop:%d, mode:%d ",
+ ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode);
+ if (conn_src->conn->mode == MGCP_CONN_LOOPBACK)
+ LOGPC(DRTP, LOGL_DEBUG, "(loopback)\n");
+ else
+ LOGPC(DRTP, LOGL_DEBUG, "\n");
+
+ /* Note: In case of loopback configuration, both, the source and the
+ * destination will point to the same connection. */
+ rtp_end = &conn_dst->end;
+ rtp_state = &conn_src->state;
+ dest_name = conn_dst->conn->name;
+
+ if (!rtp_end->output_enabled) {
+ rtp_end->dropped_packets += 1;
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x output disabled, drop to %s %s "
+ "rtp_port:%u rtcp_port:%u\n",
+ ENDPOINT_NUMBER(endp),
+ dest_name,
+ inet_ntoa(rtp_end->addr),
+ ntohs(rtp_end->rtp_port), ntohs(rtp_end->rtcp_port)
+ );
+ } else if (is_rtp) {
+ int cont;
+ int nbytes = 0;
+ int buflen = len;
+ do {
+ /* Run transcoder */
+ cont = endp->cfg->rtp_processing_cb(endp, rtp_end,
+ buf, &buflen,
+ RTP_BUF_SIZE);
+ if (cont < 0)
+ break;
+
+ if (addr)
+ mgcp_patch_and_count(endp, rtp_state, rtp_end,
+ addr, buf, buflen);
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x process/send to %s %s "
+ "rtp_port:%u rtcp_port:%u\n",
+ ENDPOINT_NUMBER(endp), dest_name,
+ inet_ntoa(rtp_end->addr), ntohs(rtp_end->rtp_port),
+ ntohs(rtp_end->rtcp_port)
+ );
+
+ /* Forward a copy of the RTP data to a debug ip/port */
+ forward_data(rtp_end->rtp.fd, &conn_src->tap_out,
+ buf, buflen);
+
+ /* FIXME: HACK HACK HACK. See OS#2459.
+ * The ip.access nano3G needs the first RTP payload's first two bytes to read hex
+ * 'e400', or it will reject the RAB assignment. It seems to not harm other femto
+ * cells (as long as we patch only the first RTP payload in each stream).
+ */
+ if (!rtp_state->patched_first_rtp_payload) {
+ uint8_t *data = (uint8_t *) & buf[12];
+ data[0] = 0xe4;
+ data[1] = 0x00;
+ rtp_state->patched_first_rtp_payload = true;
+ }
+
+ len = mgcp_udp_send(rtp_end->rtp.fd,
+ &rtp_end->addr,
+ rtp_end->rtp_port, buf, buflen);
+
+ if (len <= 0)
+ return len;
+
+ conn_dst->end.packets_tx += 1;
+ conn_dst->end.octets_tx += len;
+
+ nbytes += len;
+ buflen = cont;
+ } while (buflen > 0);
+ return nbytes;
+ } else if (!tcfg->omit_rtcp) {
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x send to %s %s rtp_port:%u rtcp_port:%u\n",
+ ENDPOINT_NUMBER(endp),
+ dest_name,
+ inet_ntoa(rtp_end->addr),
+ ntohs(rtp_end->rtp_port), ntohs(rtp_end->rtcp_port)
+ );
+
+ len = mgcp_udp_send(rtp_end->rtcp.fd,
+ &rtp_end->addr,
+ rtp_end->rtcp_port, buf, len);
+
+ conn_dst->end.packets_tx += 1;
+ conn_dst->end.octets_tx += len;
+
+ return len;
+ }
+
+ return 0;
+}
+
+/* Helper function for mgcp_recv(),
+ Receive one RTP Packet + Originating address from file descriptor */
+static int receive_from(struct mgcp_endpoint *endp, int fd,
+ struct sockaddr_in *addr, char *buf, int bufsize)
+{
+ int rc;
+ socklen_t slen = sizeof(*addr);
+ struct sockaddr_in addr_sink;
+ char buf_sink[RTP_BUF_SIZE];
+ bool tossed = false;
+
+ if (!addr)
+ addr = &addr_sink;
+ if (!buf) {
+ tossed = true;
+ buf = buf_sink;
+ bufsize = sizeof(buf_sink);
+ }
+
+ rc = recvfrom(fd, buf, bufsize, 0, (struct sockaddr *)addr, &slen);
+
+ LOGP(DRTP, LOGL_DEBUG,
+ "receiving %u bytes length packet from %s:%u ...\n",
+ rc, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
+
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x failed to receive packet, errno: %d/%s\n",
+ ENDPOINT_NUMBER(endp), errno, strerror(errno));
+ return -1;
+ }
+
+ if (tossed) {
+ LOGP(DRTP, LOGL_ERROR, "endpoint:%x packet tossed\n",
+ ENDPOINT_NUMBER(endp));
+ }
+
+ return rc;
+}
+
+/* Check if the origin (addr) matches the address/port data of the RTP
+ * connections. */
+static int check_rtp_origin(struct mgcp_conn_rtp *conn,
+ struct sockaddr_in *addr)
+{
+ struct mgcp_endpoint *endp;
+ endp = conn->conn->endp;
+
+ /* Note: Check if the inbound RTP data comes from the same host to
+ * which we send our outgoing RTP traffic. */
+ if (memcmp(&addr->sin_addr, &conn->end.addr, sizeof(addr->sin_addr))
+ != 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x data from wrong address: %s, ",
+ ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr));
+ LOGPC(DRTP, LOGL_ERROR, "expected: %s\n",
+ inet_ntoa(conn->end.addr));
+ LOGP(DRTP, LOGL_ERROR, "endpoint:%x packet tossed\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ /* Note: Usually the remote remote port of the data we receive will be
+ * the same as the remote port where we transmit outgoing RTP traffic
+ * to (set by MDCX). We use this to check the origin of the data for
+ * plausibility. */
+ if (conn->end.rtp_port != addr->sin_port &&
+ conn->end.rtcp_port != addr->sin_port) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x data from wrong source port: %d, ",
+ ENDPOINT_NUMBER(endp), ntohs(addr->sin_port));
+ LOGPC(DRTP, LOGL_ERROR,
+ "expected: %d for RTP or %d for RTCP\n",
+ ntohs(conn->end.rtp_port), ntohs(conn->end.rtcp_port));
+ LOGP(DRTP, LOGL_ERROR, "endpoint:%x packet tossed\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Check the if the destination address configuration of an RTP connection
+ * makes sense */
+static int check_rtp_destin(struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_endpoint *endp;
+ endp = conn->conn->endp;
+
+ if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x destination IP-address is invalid\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ if (conn->end.rtp_port == 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x destination rtp port is invalid\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Receive RTP data from a specified source connection and dispatch it to a
+ * destination connection. */
+static int mgcp_recv(int *proto, struct sockaddr_in *addr, char *buf,
+ unsigned int buf_size, struct osmo_fd *fd)
+{
+ struct mgcp_endpoint *endp;
+ struct mgcp_conn_rtp *conn;
+ struct mgcp_trunk_config *tcfg;
+ int rc;
+
+ conn = (struct mgcp_conn_rtp*) fd->data;
+ endp = conn->conn->endp;
+ tcfg = endp->tcfg;
+
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x receiving RTP/RTCP packet...\n",
+ ENDPOINT_NUMBER(endp));
+
+ rc = receive_from(endp, fd->fd, addr, buf, buf_size);
+ if (rc <= 0)
+ return -1;
+ *proto = fd == &conn->end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x ", ENDPOINT_NUMBER(endp));
+ LOGPC(DRTP, LOGL_DEBUG, "receiveing from %s %s %d\n",
+ conn->conn->name, inet_ntoa(addr->sin_addr),
+ ntohs(addr->sin_port));
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x conn:%s\n", ENDPOINT_NUMBER(endp),
+ mgcp_conn_dump(conn->conn));
+
+ /* Check if the origin of the RTP packet seems plausible */
+ if (tcfg->rtp_accept_all == 0) {
+ if (check_rtp_origin(conn, addr) != 0)
+ return -1;
+ }
+
+ /* Filter out dummy message */
+ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+ LOGP(DRTP, LOGL_NOTICE,
+ "endpoint:%x dummy message received\n",
+ ENDPOINT_NUMBER(endp));
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x packet tossed\n", ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ /* Increment RX statistics */
+ conn->end.packets_rx += 1;
+ conn->end.octets_rx += rc;
+
+ /* Forward a copy of the RTP data to a debug ip/port */
+ forward_data(fd->fd, &conn->tap_in, buf, rc);
+
+ return rc;
+}
+
+/* Send RTP data. Possible options are standard RTP packet
+ * transmission or trsmission via an osmux connection */
+static int mgcp_send_rtp(int proto, struct sockaddr_in *addr, char *buf,
+ unsigned int buf_size,
+ struct mgcp_conn_rtp *conn_src,
+ struct mgcp_conn_rtp *conn_dst)
+{
+ struct mgcp_endpoint *endp;
+ endp = conn_src->conn->endp;
+
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x destin conn:%s\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn_dst->conn));
+
+ /* Before we try to deliver the packet, we check if the destination
+ * port and IP-Address make sense at all. If not, we will be unable
+ * to deliver the packet. */
+ if (check_rtp_destin(conn_dst) != 0)
+ return -1;
+
+ /* Depending on the RTP connection type, deliver the RTP packet to the
+ * destination connection. */
+ switch (conn_dst->type) {
+ case MGCP_RTP_DEFAULT:
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x endpoint type is MGCP_RTP_DEFAULT, "
+ "using mgcp_send() to forward data directly\n",
+ ENDPOINT_NUMBER(endp));
+ return mgcp_send(endp, proto == MGCP_PROTO_RTP,
+ addr, buf, buf_size, conn_src, conn_dst);
+ case MGCP_OSMUX_BSC_NAT:
+ case MGCP_OSMUX_BSC:
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:%x endpoint type is MGCP_OSMUX_BSC_NAT, "
+ "using osmux_xfrm_to_osmux() to forward data through OSMUX\n",
+ ENDPOINT_NUMBER(endp));
+ return osmux_xfrm_to_osmux(buf, buf_size, conn_dst);
+ }
+
+ /* If the data has not been handled/forwarded until here, it will
+ * be discarded, this should not happen, normally the MGCP type
+ * should be properly set */
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x bad MGCP type -- data discarded!\n",
+ ENDPOINT_NUMBER(endp));
+
+ return -1;
+}
+
+/*! dispatch incoming RTP packet to opposite RTP connection.
+ * \param[in] proto protocol (MGCP_CONN_TYPE_RTP or MGCP_CONN_TYPE_RTCP)
+ * \param[in] addr socket address where the RTP packet has been received from
+ * \param[in] buf buffer that hold the RTP payload
+ * \param[in] buf_size size data length of buf
+ * \param[in] conn originating connection
+ * \returns 0 on success, -1 on ERROR */
+int mgcp_dispatch_rtp_bridge_cb(int proto, struct sockaddr_in *addr, char *buf,
+ unsigned int buf_size, struct mgcp_conn *conn)
+{
+ struct mgcp_conn *conn_dst;
+ struct mgcp_endpoint *endp;
+ endp = conn->endp;
+
+ /*! NOTE: This callback function implements the endpoint specific
+ * dispatch bahviour of an rtp bridge/proxy endpoint. It is assumed
+ * that the endpoint will hold only two connections. This premise
+ * is used to determine the opposite connection (it is always the
+ * connection that is not the originating connection). Once the
+ * destination connection is known the RTP packet is sent via
+ * the destination connection. */
+
+ /* Find a destination connection. */
+ /* NOTE: This code path runs every time an RTP packet is received. The
+ * function mgcp_find_dst_conn() we use to determine the detination
+ * connection will iterate the connection list inside the endpoint.
+ * Since list iterations are quite costly, we will figure out the
+ * destination only once and use the optional private data pointer of
+ * the connection to cache the destination connection pointer. */
+ if (!conn->priv) {
+ conn_dst = mgcp_find_dst_conn(conn);
+ conn->priv = conn_dst;
+ } else {
+ conn_dst = (struct mgcp_conn *)conn->priv;
+ }
+
+ /* There is no destination conn, stop here */
+ if (!conn_dst) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x unable to find destination conn\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ /* The destination conn is not an RTP connection */
+ if (conn_dst->type != MGCP_CONN_TYPE_RTP) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x unable to find suitable destination conn\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ /* Dispatch RTP packet to destination RTP connection */
+ return mgcp_send_rtp(proto, addr, buf,
+ buf_size, &conn->u.rtp, &conn_dst->u.rtp);
+
+}
+
+/* Handle incoming RTP data from NET */
+static int rtp_data_net(struct osmo_fd *fd, unsigned int what)
+{
+ /* NOTE: This is a generic implementation. RTP data is received. In
+ * case of loopback the data is just sent back to its origin. All
+ * other cases implement endpoint specific behaviour (e.g. how is the
+ * destination connection determined?). That specific behaviour is
+ * implemented by the callback function that is called at the end of
+ * the function */
+
+ struct mgcp_conn_rtp *conn_src;
+ struct mgcp_endpoint *endp;
+ struct sockaddr_in addr;
+
+ char buf[RTP_BUF_SIZE];
+ int proto;
+ int len;
+
+ conn_src = (struct mgcp_conn_rtp *)fd->data;
+ OSMO_ASSERT(conn_src);
+ endp = conn_src->conn->endp;
+ OSMO_ASSERT(endp);
+
+ LOGP(DRTP, LOGL_DEBUG, "endpoint:%x source conn:%s\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn_src->conn));
+
+ /* Receive packet */
+ len = mgcp_recv(&proto, &addr, buf, sizeof(buf), fd);
+ if (len < 0)
+ return -1;
+
+ /* Check if the connection is in loopback mode, if yes, just send the
+ * incoming data back to the origin */
+ if (conn_src->conn->mode == MGCP_CONN_LOOPBACK) {
+ return mgcp_send_rtp(proto, &addr, buf,
+ len, conn_src, conn_src);
+ }
+
+ /* Execute endpoint specific implementation that handles the
+ * dispatching of the RTP data */
+ return endp->type->dispatch_rtp_cb(proto, &addr, buf, len,
+ conn_src->conn);
+}
+
+/*! set IP Type of Service parameter.
+ * \param[in] fd associated file descriptor
+ * \param[in] tos dscp value
+ * \returns 0 on success, -1 on ERROR */
+int mgcp_set_ip_tos(int fd, int tos)
+{
+ int ret;
+ ret = setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+
+ if (ret < 0)
+ return -1;
+ return 0;
+}
+
+/*! bind RTP port to osmo_fd.
+ * \param[in] source_addr source (local) address to bind on
+ * \param[in] fd associated file descriptor
+ * \param[in] port to bind on
+ * \returns 0 on success, -1 on ERROR */
+int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port)
+{
+ struct sockaddr_in addr;
+ int on = 1;
+
+ fd->fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd->fd < 0) {
+ LOGP(DRTP, LOGL_ERROR, "failed to create UDP port (%s:%i).\n",
+ source_addr, port);
+ return -1;
+ } else {
+ LOGP(DRTP, LOGL_DEBUG,
+ "created UDP port (%s:%i).\n", source_addr, port);
+ }
+
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "failed to set socket options (%s:%i).\n", source_addr,
+ port);
+ return -1;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ inet_aton(source_addr, &addr.sin_addr);
+
+ if (bind(fd->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ close(fd->fd);
+ fd->fd = -1;
+ LOGP(DRTP, LOGL_ERROR, "failed to bind UDP port (%s:%i).\n",
+ source_addr, port);
+ return -1;
+ } else {
+ LOGP(DRTP, LOGL_DEBUG,
+ "bound UDP port (%s:%i).\n", source_addr, port);
+ }
+
+ return 0;
+}
+
+/* Bind RTP and RTCP port (helper function for mgcp_bind_net_rtp_port()) */
+static int bind_rtp(struct mgcp_config *cfg, const char *source_addr,
+ struct mgcp_rtp_end *rtp_end, int endpno)
+{
+ /* NOTE: The port that is used for RTCP is the RTP port incremented by one
+ * (e.g. RTP-Port = 16000 ==> RTCP-Port = 16001) */
+
+ if (mgcp_create_bind(source_addr, &rtp_end->rtp,
+ rtp_end->local_port) != 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x failed to create RTP port: %s:%d\n", endpno,
+ source_addr, rtp_end->local_port);
+ goto cleanup0;
+ }
+
+ if (mgcp_create_bind(source_addr, &rtp_end->rtcp,
+ rtp_end->local_port + 1) != 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x failed to create RTCP port: %s:%d\n", endpno,
+ source_addr, rtp_end->local_port + 1);
+ goto cleanup1;
+ }
+
+ /* Set Type of Service (DSCP-Value) as configured via VTY */
+ mgcp_set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp);
+ mgcp_set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp);
+
+ rtp_end->rtp.when = BSC_FD_READ;
+ if (osmo_fd_register(&rtp_end->rtp) != 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x failed to register RTP port %d\n", endpno,
+ rtp_end->local_port);
+ goto cleanup2;
+ }
+
+ rtp_end->rtcp.when = BSC_FD_READ;
+ if (osmo_fd_register(&rtp_end->rtcp) != 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x failed to register RTCP port %d\n", endpno,
+ rtp_end->local_port + 1);
+ goto cleanup3;
+ }
+
+ return 0;
+
+cleanup3:
+ osmo_fd_unregister(&rtp_end->rtp);
+cleanup2:
+ close(rtp_end->rtcp.fd);
+ rtp_end->rtcp.fd = -1;
+cleanup1:
+ close(rtp_end->rtp.fd);
+ rtp_end->rtp.fd = -1;
+cleanup0:
+ return -1;
+}
+
+/*! bind RTP port to endpoint/connection.
+ * \param[in] endp endpoint that holds the RTP connection
+ * \param[in] rtp_port port number to bind on
+ * \param[in] conn associated RTP connection
+ * \returns 0 on success, -1 on ERROR */
+int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port,
+ struct mgcp_conn_rtp *conn)
+{
+ char name[512];
+ struct mgcp_rtp_end *end;
+ char local_ip_addr[INET_ADDRSTRLEN];
+
+ snprintf(name, sizeof(name), "%s-%u", conn->conn->name, conn->conn->id);
+ end = &conn->end;
+
+ if (end->rtp.fd != -1 || end->rtcp.fd != -1) {
+ LOGP(DRTP, LOGL_ERROR,
+ "endpoint:%x %u was already bound on conn:%s\n",
+ ENDPOINT_NUMBER(endp), rtp_port,
+ mgcp_conn_dump(conn->conn));
+
+ /* Double bindings should never occour! Since we always allocate
+ * connections dynamically and free them when they are not
+ * needed anymore, there must be no previous binding leftover.
+ * Should there be a connection bound twice, we have a serious
+ * problem and must exit immediately! */
+ OSMO_ASSERT(false);
+ }
+
+ end->local_port = rtp_port;
+ end->rtp.cb = rtp_data_net;
+ end->rtp.data = conn;
+ end->rtcp.data = conn;
+ end->rtcp.cb = rtp_data_net;
+
+ mgcp_get_local_addr(local_ip_addr, conn);
+
+ return bind_rtp(endp->cfg, local_ip_addr, end,
+ ENDPOINT_NUMBER(endp));
+}
+
+/*! free allocated RTP and RTCP ports.
+ * \param[in] end RTP end */
+void mgcp_free_rtp_port(struct mgcp_rtp_end *end)
+{
+ if (end->rtp.fd != -1) {
+ close(end->rtp.fd);
+ end->rtp.fd = -1;
+ osmo_fd_unregister(&end->rtp);
+ }
+
+ if (end->rtcp.fd != -1) {
+ close(end->rtcp.fd);
+ end->rtcp.fd = -1;
+ osmo_fd_unregister(&end->rtcp);
+ }
+}
diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c
new file mode 100644
index 0000000..60ffe06
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_osmux.c
@@ -0,0 +1,692 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@gnumonks.org>
+ * (C) 2012-2013 by On Waves ehf <http://www.on-waves.com>
+ * All rights not specifically granted under this license are 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.
+ */
+
+#include <stdio.h> /* for printf */
+#include <string.h> /* for memcpy */
+#include <stdlib.h> /* for abs */
+#include <inttypes.h> /* for PRIu64 */
+#include <netinet/in.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/netif/osmux.h>
+#include <osmocom/netif/rtp.h>
+
+#include <osmocom/mgcp/mgcp.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/osmux.h>
+#include <osmocom/mgcp/mgcp_conn.h>
+
+static struct osmo_fd osmux_fd;
+
+static LLIST_HEAD(osmux_handle_list);
+
+struct osmux_handle {
+ struct llist_head head;
+ struct osmux_in_handle *in;
+ struct in_addr rem_addr;
+ int rem_port;
+ int refcnt;
+};
+
+static void *osmux;
+
+/* Deliver OSMUX batch to the remote end */
+static void osmux_deliver_cb(struct msgb *batch_msg, void *data)
+{
+ struct osmux_handle *handle = data;
+ struct sockaddr_in out = {
+ .sin_family = AF_INET,
+ .sin_port = handle->rem_port,
+ };
+
+ memcpy(&out.sin_addr, &handle->rem_addr, sizeof(handle->rem_addr));
+ sendto(osmux_fd.fd, batch_msg->data, batch_msg->len, 0,
+ (struct sockaddr *)&out, sizeof(out));
+ msgb_free(batch_msg);
+}
+
+/* Lookup existing OSMUX handle for specified destination address. */
+static struct osmux_handle *
+osmux_handle_find_get(struct in_addr *addr, int rem_port)
+{
+ struct osmux_handle *h;
+
+ llist_for_each_entry(h, &osmux_handle_list, head) {
+ if (memcmp(&h->rem_addr, addr, sizeof(struct in_addr)) == 0 &&
+ h->rem_port == rem_port) {
+ LOGP(DLMGCP, LOGL_DEBUG, "using existing OSMUX handle "
+ "for addr=%s:%d\n",
+ inet_ntoa(*addr), ntohs(rem_port));
+ h->refcnt++;
+ return h;
+ }
+ }
+
+ return NULL;
+}
+
+/* Put down no longer needed OSMUX handle */
+static void osmux_handle_put(struct osmux_in_handle *in)
+{
+ struct osmux_handle *h;
+
+ llist_for_each_entry(h, &osmux_handle_list, head) {
+ if (h->in == in) {
+ if (--h->refcnt == 0) {
+ LOGP(DLMGCP, LOGL_INFO,
+ "Releasing unused osmux handle for %s:%d\n",
+ inet_ntoa(h->rem_addr),
+ ntohs(h->rem_port));
+ LOGP(DLMGCP, LOGL_INFO, "Stats: "
+ "input RTP msgs: %u bytes: %"PRIu64" "
+ "output osmux msgs: %u bytes: %"PRIu64"\n",
+ in->stats.input_rtp_msgs,
+ in->stats.input_rtp_bytes,
+ in->stats.output_osmux_msgs,
+ in->stats.output_osmux_bytes);
+ llist_del(&h->head);
+ osmux_xfrm_input_fini(h->in);
+ talloc_free(h);
+ }
+ return;
+ }
+ }
+ LOGP(DLMGCP, LOGL_ERROR, "cannot find Osmux input handle %p\n", in);
+}
+
+/* Allocate free OSMUX handle */
+static struct osmux_handle *
+osmux_handle_alloc(struct mgcp_config *cfg, struct in_addr *addr, int rem_port)
+{
+ struct osmux_handle *h;
+
+ h = talloc_zero(osmux, struct osmux_handle);
+ if (!h)
+ return NULL;
+ h->rem_addr = *addr;
+ h->rem_port = rem_port;
+ h->refcnt++;
+
+ h->in = talloc_zero(h, struct osmux_in_handle);
+ if (!h->in) {
+ talloc_free(h);
+ return NULL;
+ }
+
+ /* sequence number to start OSMUX message from */
+ h->in->osmux_seq = 0;
+
+ h->in->batch_factor = cfg->osmux_batch;
+
+ /* If batch size is zero, the library defaults to 1470 bytes. */
+ h->in->batch_size = cfg->osmux_batch_size;
+ h->in->deliver = osmux_deliver_cb;
+ osmux_xfrm_input_init(h->in);
+ h->in->data = h;
+
+ llist_add(&h->head, &osmux_handle_list);
+
+ LOGP(DLMGCP, LOGL_DEBUG, "created new OSMUX handle for addr=%s:%d\n",
+ inet_ntoa(*addr), ntohs(rem_port));
+
+ return h;
+}
+
+/* Lookup existing handle for a specified address, if the handle can not be
+ * foud a the function will automatically allocate one */
+static struct osmux_in_handle *
+osmux_handle_lookup(struct mgcp_config *cfg, struct in_addr *addr, int rem_port)
+{
+ struct osmux_handle *h;
+
+ h = osmux_handle_find_get(addr, rem_port);
+ if (h != NULL)
+ return h->in;
+
+ h = osmux_handle_alloc(cfg, addr, rem_port);
+ if (h == NULL)
+ return NULL;
+
+ return h->in;
+}
+
+/*! send RTP packet through OSMUX connection.
+ * \param[in] buf rtp data
+ * \param[in] buf_len length of rtp data
+ * \param[in] conn associated RTP connection
+ * \returns 0 on success, -1 on ERROR */
+int osmux_xfrm_to_osmux(char *buf, int buf_len, struct mgcp_conn_rtp *conn)
+{
+ int ret;
+ struct msgb *msg;
+
+ msg = msgb_alloc(4096, "RTP");
+ if (!msg)
+ return -1;
+
+ memcpy(msg->data, buf, buf_len);
+ msgb_put(msg, buf_len);
+
+ while ((ret = osmux_xfrm_input(conn->osmux.in, msg, conn->osmux.cid)) > 0) {
+ /* batch full, build and deliver it */
+ osmux_xfrm_input_deliver(conn->osmux.in);
+ }
+ return 0;
+}
+
+/* Lookup the endpoint that corresponds to the specified address (port) */
+static struct mgcp_endpoint *
+endpoint_lookup(struct mgcp_config *cfg, int cid,
+ struct in_addr *from_addr, int type)
+{
+ struct mgcp_endpoint *endp = NULL;
+ int i;
+ struct mgcp_conn_rtp *conn_net = NULL;
+ struct mgcp_conn_rtp *conn_bts = NULL;
+
+ for (i=0; i<cfg->trunk.number_endpoints; i++) {
+ struct in_addr *this;
+
+ endp = &cfg->trunk.endpoints[i];
+
+#if 0
+ if (!tmp->allocated)
+ continue;
+#endif
+
+ switch(type) {
+ case MGCP_DEST_NET:
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ this = &conn_net->end.addr;
+ break;
+ case MGCP_DEST_BTS:
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS);
+ this = &conn_bts->end.addr;
+ break;
+ default:
+ /* Should not ever happen */
+ LOGP(DLMGCP, LOGL_ERROR, "Bad type %d. Fix your code.\n", type);
+ return NULL;
+ }
+
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ if (conn_net->osmux.cid == cid && this->s_addr == from_addr->s_addr)
+ return endp;
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot find endpoint with cid=%d\n", cid);
+
+ return NULL;
+}
+
+static void scheduled_tx_net_cb(struct msgb *msg, void *data)
+{
+ struct mgcp_endpoint *endp = data;
+ struct mgcp_conn_rtp *conn_net = NULL;
+ struct mgcp_conn_rtp *conn_bts = NULL;
+
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS);
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ if (!conn_bts || !conn_net)
+ return;
+
+ struct sockaddr_in addr = {
+ .sin_addr = conn_net->end.addr,
+ .sin_port = conn_net->end.rtp_port,
+ };
+
+ conn_bts->end.octets_tx += msg->len;
+ conn_bts->end.packets_tx++;
+
+ /* Send RTP data to NET */
+ /* FIXME: Get rid of conn_bts and conn_net! */
+ mgcp_send(endp, 1, &addr, (char *)msg->data, msg->len,
+ conn_bts, conn_net);
+ msgb_free(msg);
+}
+
+static void scheduled_tx_bts_cb(struct msgb *msg, void *data)
+{
+ struct mgcp_endpoint *endp = data;
+ struct mgcp_conn_rtp *conn_net = NULL;
+ struct mgcp_conn_rtp *conn_bts = NULL;
+
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS);
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ if (!conn_bts || !conn_net)
+ return;
+
+ struct sockaddr_in addr = {
+ .sin_addr = conn_bts->end.addr,
+ .sin_port = conn_bts->end.rtp_port,
+ };
+
+ conn_net->end.octets_tx += msg->len;
+ conn_net->end.packets_tx++;
+
+ /* Send RTP data to BTS */
+ /* FIXME: Get rid of conn_bts and conn_net! */
+ mgcp_send(endp, 1, &addr, (char *)msg->data, msg->len,
+ conn_net, conn_bts);
+ msgb_free(msg);
+}
+
+static struct msgb *osmux_recv(struct osmo_fd *ofd, struct sockaddr_in *addr)
+{
+ struct msgb *msg;
+ socklen_t slen = sizeof(*addr);
+ int ret;
+
+ msg = msgb_alloc(4096, "OSMUX");
+ if (!msg) {
+ LOGP(DLMGCP, LOGL_ERROR, "cannot allocate message\n");
+ return NULL;
+ }
+ ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0,
+ (struct sockaddr *)addr, &slen);
+ if (ret <= 0) {
+ msgb_free(msg);
+ LOGP(DLMGCP, LOGL_ERROR, "cannot receive message\n");
+ return NULL;
+ }
+ msgb_put(msg, ret);
+
+ return msg;
+}
+
+#define osmux_chunk_length(msg, rem) (rem - msg->len);
+
+int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct msgb *msg;
+ struct osmux_hdr *osmuxh;
+ struct llist_head list;
+ struct sockaddr_in addr;
+ struct mgcp_config *cfg = ofd->data;
+ uint32_t rem;
+ struct mgcp_conn_rtp *conn_net = NULL;
+
+ msg = osmux_recv(ofd, &addr);
+ if (!msg)
+ return -1;
+
+ /* not any further processing dummy messages */
+ if (msg->data[0] == MGCP_DUMMY_LOAD)
+ goto out;
+
+ rem = msg->len;
+ while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) {
+ struct mgcp_endpoint *endp;
+
+ /* Yes, we use MGCP_DEST_NET to locate the origin */
+ endp = endpoint_lookup(cfg, osmuxh->circuit_id,
+ &addr.sin_addr, MGCP_DEST_NET);
+
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ if (!conn_net)
+ goto out;
+
+ if (!endp) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot find an endpoint for circuit_id=%d\n",
+ osmuxh->circuit_id);
+ goto out;
+ }
+ conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem);
+ conn_net->osmux.stats.chunks++;
+ rem = msg->len;
+
+ osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list);
+ osmux_tx_sched(&list, scheduled_tx_bts_cb, endp);
+ }
+out:
+ msgb_free(msg);
+ return 0;
+}
+
+/* This is called from the bsc-nat */
+static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr,
+ struct msgb *msg)
+{
+ struct mgcp_endpoint *endp;
+ uint8_t osmux_cid;
+ struct mgcp_conn_rtp *conn_net = NULL;
+
+ if (msg->len < 1 + sizeof(osmux_cid)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Discarding truncated Osmux dummy load\n");
+ goto out;
+ }
+
+ LOGP(DLMGCP, LOGL_DEBUG, "Received Osmux dummy load from %s\n",
+ inet_ntoa(addr->sin_addr));
+
+ if (!cfg->osmux) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "bsc wants to use Osmux but bsc-nat did not request it\n");
+ goto out;
+ }
+
+ /* extract the osmux CID from the dummy message */
+ memcpy(&osmux_cid, &msg->data[1], sizeof(osmux_cid));
+
+ endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, MGCP_DEST_BTS);
+ if (!endp) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot find endpoint for Osmux CID %d\n", osmux_cid);
+ goto out;
+ }
+
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ if (!conn_net)
+ goto out;
+
+ if (conn_net->osmux.state == OSMUX_STATE_ENABLED)
+ goto out;
+
+ if (osmux_enable_conn(endp, conn_net, &addr->sin_addr, addr->sin_port) < 0 ) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Could not enable osmux in endpoint %d\n",
+ ENDPOINT_NUMBER(endp));
+ goto out;
+ }
+
+ LOGP(DLMGCP, LOGL_INFO, "Enabling osmux in endpoint %d for %s:%u\n",
+ ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr),
+ ntohs(addr->sin_port));
+out:
+ msgb_free(msg);
+ return 0;
+}
+
+int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct msgb *msg;
+ struct osmux_hdr *osmuxh;
+ struct llist_head list;
+ struct sockaddr_in addr;
+ struct mgcp_config *cfg = ofd->data;
+ uint32_t rem;
+ struct mgcp_conn_rtp *conn_net = NULL;
+
+ msg = osmux_recv(ofd, &addr);
+ if (!msg)
+ return -1;
+
+ /* not any further processing dummy messages */
+ if (msg->data[0] == MGCP_DUMMY_LOAD)
+ return osmux_handle_dummy(cfg, &addr, msg);
+
+ rem = msg->len;
+ while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) {
+ struct mgcp_endpoint *endp;
+
+ /* Yes, we use MGCP_DEST_BTS to locate the origin */
+ endp = endpoint_lookup(cfg, osmuxh->circuit_id,
+ &addr.sin_addr, MGCP_DEST_BTS);
+
+ /* FIXME: Get rid of CONN_ID_XXX! */
+ conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET);
+ if (!conn_net)
+ goto out;
+
+ if (!endp) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot find an endpoint for circuit_id=%d\n",
+ osmuxh->circuit_id);
+ goto out;
+ }
+ conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem);
+ conn_net->osmux.stats.chunks++;
+ rem = msg->len;
+
+ osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list);
+ osmux_tx_sched(&list, scheduled_tx_net_cb, endp);
+ }
+out:
+ msgb_free(msg);
+ return 0;
+}
+
+int osmux_init(int role, struct mgcp_config *cfg)
+{
+ int ret;
+
+ switch(role) {
+ case OSMUX_ROLE_BSC:
+ osmux_fd.cb = osmux_read_from_bsc_nat_cb;
+ break;
+ case OSMUX_ROLE_BSC_NAT:
+ osmux_fd.cb = osmux_read_from_bsc_cb;
+ break;
+ default:
+ LOGP(DLMGCP, LOGL_ERROR, "wrong role for OSMUX\n");
+ return -1;
+ }
+ osmux_fd.data = cfg;
+
+ ret = mgcp_create_bind(cfg->osmux_addr, &osmux_fd, cfg->osmux_port);
+ if (ret < 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "cannot bind OSMUX socket\n");
+ return ret;
+ }
+ mgcp_set_ip_tos(osmux_fd.fd, cfg->endp_dscp);
+ osmux_fd.when |= BSC_FD_READ;
+
+ ret = osmo_fd_register(&osmux_fd);
+ if (ret < 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "cannot register OSMUX socket\n");
+ return ret;
+ }
+ cfg->osmux_init = 1;
+
+ return 0;
+}
+
+/*! enable OSXMUX circuit for a specified connection.
+ * \param[in] endp mgcp endpoint (configuration)
+ * \param[in] conn connection to disable
+ * \param[in] addr IP address of remote OSMUX endpoint
+ * \param[in] port portnumber of the remote OSMUX endpoint
+ * \returns 0 on success, -1 on ERROR */
+int osmux_enable_conn(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn,
+ struct in_addr *addr, uint16_t port)
+{
+ /*! If osmux is enabled, initialize the output handler. This handler is
+ * used to reconstruct the RTP flow from osmux. The RTP SSRC is
+ * allocated based on the circuit ID (conn_net->osmux.cid), which is unique
+ * in the local scope to the BSC/BSC-NAT. We use it to divide the RTP
+ * SSRC space (2^32) by the 256 possible circuit IDs, then randomly
+ * select one value from that window. Thus, we have no chance to have
+ * overlapping RTP SSRC traveling to the BTSes behind the BSC,
+ * similarly, for flows traveling to the MSC.
+ */
+ static const uint32_t rtp_ssrc_winlen = UINT32_MAX / 256;
+ uint16_t osmux_dummy = endp->cfg->osmux_dummy;
+
+ /* Check if osmux is enabled for the specified connection */
+ if (conn->osmux.state == OSMUX_STATE_DISABLED) {
+ LOGP(DLMGCP, LOGL_ERROR, "OSMUX not enabled for conn:%s\n",
+ mgcp_conn_dump(conn->conn));
+ return -1;
+ }
+
+ osmux_xfrm_output_init(&conn->osmux.out,
+ (conn->osmux.cid * rtp_ssrc_winlen) +
+ (random() % rtp_ssrc_winlen));
+
+ conn->osmux.in = osmux_handle_lookup(endp->cfg, addr, port);
+ if (!conn->osmux.in) {
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot allocate input osmux handle for conn:%s\n",
+ mgcp_conn_dump(conn->conn));
+ return -1;
+ }
+ if (!osmux_xfrm_input_open_circuit(conn->osmux.in, conn->osmux.cid, osmux_dummy)) {
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot open osmux circuit %u for conn:%s\n",
+ conn->osmux.cid, mgcp_conn_dump(conn->conn));
+ return -1;
+ }
+
+ switch (endp->cfg->role) {
+ case MGCP_BSC_NAT:
+ conn->type = MGCP_OSMUX_BSC_NAT;
+ break;
+ case MGCP_BSC:
+ conn->type = MGCP_OSMUX_BSC;
+ break;
+ }
+
+ conn->osmux.state = OSMUX_STATE_ENABLED;
+
+ return 0;
+}
+
+/*! disable OSXMUX circuit for a specified connection.
+ * \param[in] conn connection to disable */
+void osmux_disable_conn(struct mgcp_conn_rtp *conn)
+{
+ if (!conn)
+ return;
+
+ if (conn->osmux.state != OSMUX_STATE_ENABLED)
+ return;
+
+ LOGP(DLMGCP, LOGL_INFO, "Releasing connection %u using Osmux CID %u\n",
+ conn->conn->id, conn->osmux.cid);
+ osmux_xfrm_input_close_circuit(conn->osmux.in, conn->osmux.cid);
+ conn->osmux.state = OSMUX_STATE_DISABLED;
+ conn->osmux.cid = -1;
+ osmux_handle_put(conn->osmux.in);
+}
+
+/*! relase OSXMUX cid, that had been allocated to this connection.
+ * \param[in] conn connection with OSMUX cid to release */
+void osmux_release_cid(struct mgcp_conn_rtp *conn)
+{
+ if (!conn)
+ return;
+
+ if (conn->osmux.state != OSMUX_STATE_ENABLED)
+ return;
+
+ if (conn->osmux.allocated_cid >= 0)
+ osmux_put_cid(conn->osmux.allocated_cid);
+ conn->osmux.allocated_cid = -1;
+}
+
+/*! allocate OSXMUX cid to connection.
+ * \param[in] conn connection for which we allocate the OSMUX cid*/
+void osmux_allocate_cid(struct mgcp_conn_rtp *conn)
+{
+ osmux_release_cid(conn);
+ conn->osmux.allocated_cid = osmux_get_cid();
+}
+
+/*! send RTP dummy packet to OSMUX connection port.
+ * \param[in] endp mcgp endpoint that holds the RTP connection
+ * \param[in] conn associated RTP connection
+ * \returns bytes sent, -1 on error */
+int osmux_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn)
+{
+ char buf[1 + sizeof(uint8_t)];
+ struct in_addr addr_unset = {};
+
+ /*! The dummy packet will not be sent via the actual OSMUX connection,
+ * instead it is sent out of band to port where the remote OSMUX
+ * multplexer is listening. The goal is to ensure that the connection
+ * is kept open */
+
+ /*! We don't need to send the dummy load for osmux so often as another
+ * endpoint may have already punched the hole in the firewall. This
+ * approach is simple though. */
+
+ buf[0] = MGCP_DUMMY_LOAD;
+ memcpy(&buf[1], &conn->osmux.cid, sizeof(conn->osmux.cid));
+
+ /* Wait until we have the connection information from MDCX */
+ if (memcmp(&conn->end.addr, &addr_unset, sizeof(addr_unset)) == 0)
+ return 0;
+
+ if (conn->osmux.state == OSMUX_STATE_ACTIVATING) {
+ if (osmux_enable_conn(endp, conn, &conn->end.addr,
+ htons(endp->cfg->osmux_port)) < 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Could not activate osmux for conn:%s\n",
+ mgcp_conn_dump(conn->conn));
+ }
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Osmux CID %u for %s:%u is now enabled\n",
+ conn->osmux.cid, inet_ntoa(conn->end.addr),
+ endp->cfg->osmux_port);
+ }
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "sending OSMUX dummy load to %s CID %u\n",
+ inet_ntoa(conn->end.addr), conn->osmux.cid);
+
+ return mgcp_udp_send(osmux_fd.fd, &conn->end.addr,
+ htons(endp->cfg->osmux_port), buf, sizeof(buf));
+}
+
+/*! bsc-nat allocates/releases the OSMUX cids (Circuit IDs). */
+static uint8_t osmux_cid_bitmap[(OSMUX_CID_MAX + 1) / 8];
+
+/*! count the number of taken OSMUX cids.
+ * \returns number of OSMUX cids in use */
+int osmux_used_cid(void)
+{
+ int i, j, used = 0;
+
+ for (i = 0; i < sizeof(osmux_cid_bitmap); i++) {
+ for (j = 0; j < 8; j++) {
+ if (osmux_cid_bitmap[i] & (1 << j))
+ used += 1;
+ }
+ }
+
+ return used;
+}
+
+/*! take a free OSMUX cid.
+ * \returns OSMUX cid */
+int osmux_get_cid(void)
+{
+ int i, j;
+
+ for (i = 0; i < sizeof(osmux_cid_bitmap); i++) {
+ for (j = 0; j < 8; j++) {
+ if (osmux_cid_bitmap[i] & (1 << j))
+ continue;
+
+ osmux_cid_bitmap[i] |= (1 << j);
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "Allocating Osmux CID %u from pool\n", (i * 8) + j);
+ return (i * 8) + j;
+ }
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR, "All Osmux circuits are in use!\n");
+ return -1;
+}
+
+/*! put back a no longer used OSMUX cid.
+ * \param[in] osmux_cid OSMUX cid */
+void osmux_put_cid(uint8_t osmux_cid)
+{
+ LOGP(DLMGCP, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid);
+ osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8));
+}
diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c
new file mode 100644
index 0000000..8c6bd6e
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_protocol.c
@@ -0,0 +1,1293 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/mgcp/mgcp.h>
+#include <osmocom/mgcp/mgcp_common.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_stat.h>
+#include <osmocom/mgcp/mgcp_msg.h>
+#include <osmocom/mgcp/mgcp_ep.h>
+#include <osmocom/mgcp/mgcp_sdp.h>
+
+struct mgcp_request {
+ char *name;
+ struct msgb *(*handle_request) (struct mgcp_parse_data * data);
+ char *debug_name;
+};
+
+#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \
+ { .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME },
+
+static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *data);
+static struct msgb *handle_create_con(struct mgcp_parse_data *data);
+static struct msgb *handle_delete_con(struct mgcp_parse_data *data);
+static struct msgb *handle_modify_con(struct mgcp_parse_data *data);
+static struct msgb *handle_rsip(struct mgcp_parse_data *data);
+static struct msgb *handle_noti_req(struct mgcp_parse_data *data);
+
+/* Initalize transcoder */
+static int setup_rtp_processing(struct mgcp_endpoint *endp,
+ struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_config *cfg = endp->cfg;
+ struct mgcp_conn_rtp *conn_src = NULL;
+ struct mgcp_conn_rtp *conn_dst = conn;
+ struct mgcp_conn *_conn;
+
+ if (conn->type != MGCP_RTP_DEFAULT) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "endpoint:%x RTP-setup: Endpoint is not configured as RTP default, stopping here!\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ if (conn->conn->mode == MGCP_CONN_LOOPBACK) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "endpoint:%x RTP-setup: Endpoint is in loopback mode, stopping here!\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ /* Find the "sister" connection */
+ llist_for_each_entry(_conn, &endp->conns, entry) {
+ if (_conn->id != conn->conn->id) {
+ conn_src = &_conn->u.rtp;
+ break;
+ }
+ }
+
+ return cfg->setup_rtp_processing_cb(endp, &conn_dst->end,
+ &conn_src->end);
+}
+
+/* array of function pointers for handling various
+ * messages. In the future this might be binary sorted
+ * for performance reasons. */
+static const struct mgcp_request mgcp_requests[] = {
+ MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint")
+ MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection")
+ MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection")
+ MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection")
+ MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest")
+
+ /* SPEC extension */
+ MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress")
+};
+
+/* Helper function to allocate some memory for responses and retransmissions */
+static struct msgb *mgcp_msgb_alloc(void)
+{
+ struct msgb *msg;
+ msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+ if (!msg)
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n");
+
+ return msg;
+}
+
+/* Helper function for do_retransmission() and create_resp() */
+static struct msgb *do_retransmission(const struct mgcp_endpoint *endp)
+{
+ struct msgb *msg = mgcp_msgb_alloc();
+ if (!msg)
+ return NULL;
+
+ msg->l2h = msgb_put(msg, strlen(endp->last_response));
+ memcpy(msg->l2h, endp->last_response, msgb_l2len(msg));
+ mgcp_disp_msg(msg->l2h, msgb_l2len(msg), "Retransmitted response");
+ return msg;
+}
+
+static struct msgb *create_resp(struct mgcp_endpoint *endp, int code,
+ const char *txt, const char *msg,
+ const char *trans, const char *param,
+ const char *sdp)
+{
+ int len;
+ struct msgb *res;
+
+ res = mgcp_msgb_alloc();
+ if (!res)
+ return NULL;
+
+ len = snprintf((char *)res->data, 2048, "%d %s%s%s\r\n%s",
+ code, trans, txt, param ? param : "", sdp ? sdp : "");
+ if (len < 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n");
+ msgb_free(res);
+ return NULL;
+ }
+
+ res->l2h = msgb_put(res, len);
+ LOGP(DLMGCP, LOGL_DEBUG, "Generated response: code=%d\n", code);
+ mgcp_disp_msg(res->l2h, msgb_l2len(res), "Generated response");
+
+ /*
+ * Remember the last transmission per endpoint.
+ */
+ if (endp) {
+ struct mgcp_trunk_config *tcfg = endp->tcfg;
+ talloc_free(endp->last_response);
+ talloc_free(endp->last_trans);
+ endp->last_trans = talloc_strdup(tcfg->endpoints, trans);
+ endp->last_response = talloc_strndup(tcfg->endpoints,
+ (const char *)res->l2h,
+ msgb_l2len(res));
+ }
+
+ return res;
+}
+
+static struct msgb *create_ok_resp_with_param(struct mgcp_endpoint *endp,
+ int code, const char *msg,
+ const char *trans,
+ const char *param)
+{
+ return create_resp(endp, code, " OK", msg, trans, param, NULL);
+}
+
+static struct msgb *create_ok_response(struct mgcp_endpoint *endp,
+ int code, const char *msg,
+ const char *trans)
+{
+ return create_ok_resp_with_param(endp, code, msg, trans, NULL);
+}
+
+static struct msgb *create_err_response(struct mgcp_endpoint *endp,
+ int code, const char *msg,
+ const char *trans)
+{
+ return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL);
+}
+
+/* Format MGCP response string (with SDP attached) */
+static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
+ struct mgcp_conn_rtp *conn,
+ const char *msg,
+ const char *trans_id)
+{
+ const char *addr = endp->cfg->local_ip;
+ struct msgb *sdp;
+ int rc;
+ struct msgb *result;
+ char osmux_extension[strlen("\nX-Osmux: 255") + 1];
+ char local_ip_addr[INET_ADDRSTRLEN];
+
+ sdp = msgb_alloc_headroom(4096, 128, "sdp record");
+ if (!sdp)
+ return NULL;
+
+ if (!addr) {
+ mgcp_get_local_addr(local_ip_addr, conn);
+ addr = local_ip_addr;
+ }
+
+ if (conn->osmux.state == OSMUX_STATE_NEGOTIATING) {
+ sprintf(osmux_extension, "\nX-Osmux: %u", conn->osmux.cid);
+ conn->osmux.state = OSMUX_STATE_ACTIVATING;
+ } else {
+ osmux_extension[0] = '\0';
+ }
+
+ rc = msgb_printf(sdp, "I: %u%s\n\n", conn->conn->id, osmux_extension);
+ if (rc < 0)
+ goto error;
+
+ rc = mgcp_write_response_sdp(endp, conn, sdp, addr);
+ if (rc < 0)
+ goto error;
+ result = create_resp(endp, 200, " OK", msg, trans_id, NULL, (char*) sdp->data);
+ msgb_free(sdp);
+ return result;
+error:
+ msgb_free(sdp);
+ return NULL;
+}
+
+/* Send out dummy packet to keep the connection open, if the connection is an
+ * osmux connection, send the dummy packet via OSMUX */
+static void send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn)
+{
+ if (conn->osmux.state != OSMUX_STATE_DISABLED)
+ osmux_send_dummy(endp, conn);
+ else
+ mgcp_send_dummy(endp, conn);
+}
+
+/* handle incoming messages:
+ * - this can be a command (four letters, space, transaction id)
+ * - or a response (three numbers, space, transaction id) */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
+{
+ struct mgcp_parse_data pdata;
+ int i, code, handled = 0;
+ struct msgb *resp = NULL;
+ char *data;
+
+ if (msgb_l2len(msg) < 4) {
+ LOGP(DLMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len);
+ return NULL;
+ }
+
+ if (mgcp_msg_terminate_nul(msg))
+ return NULL;
+
+ mgcp_disp_msg(msg->l2h, msgb_l2len(msg), "Received message");
+
+ /* attempt to treat it as a response */
+ if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) {
+ LOGP(DLMGCP, LOGL_DEBUG, "Response: Code: %d\n", code);
+ return NULL;
+ }
+
+ msg->l3h = &msg->l2h[4];
+
+ /*
+ * Check for a duplicate message and respond.
+ */
+ memset(&pdata, 0, sizeof(pdata));
+ pdata.cfg = cfg;
+ data = mgcp_strline((char *)msg->l3h, &pdata.save);
+ pdata.found = mgcp_parse_header(&pdata, data);
+ if (pdata.endp && pdata.trans
+ && pdata.endp->last_trans
+ && strcmp(pdata.endp->last_trans, pdata.trans) == 0) {
+ return do_retransmission(pdata.endp);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i) {
+ if (strncmp
+ (mgcp_requests[i].name, (const char *)&msg->l2h[0],
+ 4) == 0) {
+ handled = 1;
+ resp = mgcp_requests[i].handle_request(&pdata);
+ break;
+ }
+ }
+
+ if (!handled)
+ LOGP(DLMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n",
+ &msg->l2h[0]);
+
+ return resp;
+}
+
+/* AUEP command handler, processes the received command */
+static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p)
+{
+ LOGP(DLMGCP, LOGL_NOTICE, "AUEP: auditing endpoint ...\n");
+
+ if (p->found != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "AUEP: failed to find the endpoint.\n");
+ return create_err_response(NULL, 500, "AUEP", p->trans);
+ } else
+ return create_ok_response(p->endp, 200, "AUEP", p->trans);
+}
+
+/* Try to find a free port by attemting to bind on it. Also handle the
+ * counter that points on the next free port. Since we have a pointer
+ * to the next free port, binding should work on the first attemt,
+ * neverless, try at least the next 200 ports before giving up */
+static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn)
+{
+ int i;
+ struct mgcp_rtp_end *end;
+ struct mgcp_port_range *range;
+
+ OSMO_ASSERT(conn);
+ end = &conn->end;
+ OSMO_ASSERT(end);
+
+ range = &endp->cfg->net_ports;
+
+ /* attempt to find a port */
+ for (i = 0; i < 200; ++i) {
+ int rc;
+
+ if (range->last_port >= range->range_end)
+ range->last_port = range->range_start;
+
+ rc = mgcp_bind_net_rtp_port(endp, range->last_port, conn);
+
+ range->last_port += 2;
+ if (rc == 0) {
+ return 0;
+ }
+
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Allocating a RTP/RTCP port failed 200 times 0x%x.\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+}
+
+/* Set the LCO from a string (see RFC 3435).
+ * The string is stored in the 'string' field. A NULL string is handled excatlyy
+ * like an empty string, the 'string' field is never NULL after this function
+ * has been called. */
+static void set_local_cx_options(void *ctx, struct mgcp_lco *lco,
+ const char *options)
+{
+ char *p_opt, *a_opt;
+ char codec[9];
+
+ talloc_free(lco->string);
+ talloc_free(lco->codec);
+ lco->codec = NULL;
+ lco->pkt_period_min = lco->pkt_period_max = 0;
+ lco->string = talloc_strdup(ctx, options ? options : "");
+
+ p_opt = strstr(lco->string, "p:");
+ if (p_opt && sscanf(p_opt, "p:%d-%d",
+ &lco->pkt_period_min, &lco->pkt_period_max) == 1)
+ lco->pkt_period_max = lco->pkt_period_min;
+
+ a_opt = strstr(lco->string, "a:");
+ if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1)
+ lco->codec = talloc_strdup(ctx, codec);
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "local CX options: lco->pkt_period_max: %i, lco->codec: %s\n",
+ lco->pkt_period_max, lco->codec);
+}
+
+void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change,
+ struct mgcp_rtp_end *rtp)
+{
+ struct mgcp_trunk_config *tcfg = endp->tcfg;
+
+ int patch_ssrc = expect_ssrc_change && tcfg->force_constant_ssrc;
+
+ rtp->force_aligned_timing = tcfg->force_aligned_timing;
+ rtp->force_constant_ssrc = patch_ssrc ? 1 : 0;
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "Configuring RTP endpoint: local port %d%s%s\n",
+ ntohs(rtp->rtp_port),
+ rtp->force_aligned_timing ? ", force constant timing" : "",
+ rtp->force_constant_ssrc ? ", force constant ssrc" : "");
+}
+
+uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *rtp)
+{
+ int f = 0;
+
+ /* Get the number of frames per channel and packet */
+ if (rtp->frames_per_packet)
+ f = rtp->frames_per_packet;
+ else if (rtp->packet_duration_ms && rtp->codec.frame_duration_num) {
+ int den = 1000 * rtp->codec.frame_duration_num;
+ f = (rtp->packet_duration_ms * rtp->codec.frame_duration_den +
+ den / 2)
+ / den;
+ }
+
+ return rtp->codec.rate * f * rtp->codec.frame_duration_num /
+ rtp->codec.frame_duration_den;
+}
+
+static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line)
+{
+ if (!endp->cfg->osmux_init) {
+ if (osmux_init(OSMUX_ROLE_BSC, endp->cfg) < 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot init OSMUX\n");
+ return -1;
+ }
+ LOGP(DLMGCP, LOGL_NOTICE, "OSMUX socket has been set up\n");
+ }
+
+ return mgcp_parse_osmux_cid(line);
+}
+
+/* CRCX command handler, processes the received command */
+static struct msgb *handle_create_con(struct mgcp_parse_data *p)
+{
+ struct mgcp_trunk_config *tcfg;
+ struct mgcp_endpoint *endp = p->endp;
+ int error_code = 400;
+
+ const char *local_options = NULL;
+ const char *callid = NULL;
+ const char *ci = NULL;
+ const char *mode = NULL;
+ char *line;
+ int have_sdp = 0, osmux_cid = -1;
+ struct mgcp_conn_rtp *conn = NULL;
+ uint32_t conn_id;
+ char conn_name[512];
+
+ LOGP(DLMGCP, LOGL_NOTICE, "CRCX: creating new connection ...\n");
+
+ if (p->found != 0)
+ return create_err_response(NULL, 510, "CRCX", p->trans);
+
+ /* parse CallID C: and LocalParameters L: */
+ for_each_line(line, p->save) {
+ if (!mgcp_check_param(endp, line))
+ continue;
+
+ switch (line[0]) {
+ case 'L':
+ local_options = (const char *)line + 3;
+ break;
+ case 'C':
+ callid = (const char *)line + 3;
+ break;
+ case 'I':
+ ci = (const char *)line + 3;
+ break;
+ case 'M':
+ mode = (const char *)line + 3;
+ break;
+ case 'X':
+ /* If osmoux is disabled, just skip setting it up */
+ if (!p->endp->cfg->osmux)
+ break;
+ if (strncmp("Osmux: ", line + 2, strlen("Osmux: ")) ==
+ 0)
+ osmux_cid = mgcp_osmux_setup(endp, line);
+ break;
+ case '\0':
+ have_sdp = 1;
+ goto mgcp_header_done;
+ default:
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "CRCX: endpoint:%x unhandled option: '%c'/%d\n",
+ ENDPOINT_NUMBER(endp), *line, *line);
+ break;
+ }
+ }
+
+mgcp_header_done:
+ tcfg = p->endp->tcfg;
+
+ /* Check parameters */
+ if (!callid) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x insufficient parameters, missing callid\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+
+ if (!mode) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x insufficient parameters, missing mode\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+
+ if (!ci) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x insufficient parameters, missing connection id\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+
+ /* Check if we are able to accept the creation of another connection */
+ if (llist_count(&endp->conns) >= endp->type->max_conns) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x endpoint full, max. %i connections allowed!\n",
+ endp->type->max_conns, ENDPOINT_NUMBER(endp));
+ if (tcfg->force_realloc) {
+ /* There is no more room for a connection, make some
+ * room by blindly tossing the oldest of the two two
+ * connections */
+ mgcp_conn_free_oldest(endp);
+ } else {
+ /* There is no more room for a connection, leave
+ * everything as it is and return with an error */
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+ }
+
+ /* Check if this endpoint already serves a call, if so, check if the
+ * callids match up so that we are sure that this is our call */
+ if (endp->callid && mgcp_verify_call_id(endp, callid)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x allready seized by other call (%s)\n",
+ ENDPOINT_NUMBER(endp), endp->callid);
+ if (tcfg->force_realloc)
+ /* This is not our call, toss everything by releasing
+ * the entire endpoint. (rude!) */
+ mgcp_release_endp(endp);
+ else {
+ /* This is not our call, leave everything as it is and
+ * return with an error. */
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+ }
+
+ /* Set the callid, creation of another connection will only be possible
+ * when the callid matches up. (Connections are distinuished by their
+ * connection ids) */
+ endp->callid = talloc_strdup(tcfg->endpoints, callid);
+
+ /* Extract audio codec information */
+ set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
+ local_options);
+
+ if (mgcp_parse_ci(&conn_id, ci)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x insufficient parameters, missing ci (connectionIdentifier)\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+
+ /* Only accept another connection when the connection ID is different. */
+ if (mgcp_conn_get_rtp(endp, conn_id)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x there is already a connection with id %u present!\n",
+ conn_id, ENDPOINT_NUMBER(endp));
+ if (tcfg->force_realloc) {
+ /* Ignore the existing connection by just freeing it */
+ mgcp_conn_free(endp, conn_id);
+ } else {
+ /* There is already a connection with that ID present,
+ * leave everything as it is and return with an error. */
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+ }
+
+ snprintf(conn_name, sizeof(conn_name), "%s-%u", callid, conn_id);
+ mgcp_conn_alloc(NULL, endp, conn_id, MGCP_CONN_TYPE_RTP,
+ conn_name);
+ conn = mgcp_conn_get_rtp(endp, conn_id);
+ if (!conn) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x unable to allocate RTP connection\n",
+ ENDPOINT_NUMBER(endp));
+ goto error2;
+
+ }
+
+ if (mgcp_parse_conn_mode(mode, endp, conn->conn) != 0) {
+ error_code = 517;
+ goto error2;
+ }
+
+ /* Annotate Osmux circuit ID and set it to negotiating state until this
+ * is fully set up from the dummy load. */
+ conn->osmux.state = OSMUX_STATE_DISABLED;
+ if (osmux_cid >= 0) {
+ conn->osmux.cid = osmux_cid;
+ conn->osmux.state = OSMUX_STATE_NEGOTIATING;
+ } else if (endp->cfg->osmux == OSMUX_USAGE_ONLY) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x osmux only and no osmux offered\n",
+ ENDPOINT_NUMBER(endp));
+ goto error2;
+ }
+
+ /* set up RTP media parameters */
+ if (have_sdp)
+ mgcp_parse_sdp_data(endp, conn, p);
+ else if (endp->local_options.codec)
+ mgcp_set_audio_info(p->cfg, &conn->end.codec,
+ PTYPE_UNDEFINED, endp->local_options.codec);
+ conn->end.fmtp_extra = talloc_strdup(tcfg->endpoints,
+ tcfg->audio_fmtp_extra);
+
+ if (p->cfg->force_ptime) {
+ conn->end.packet_duration_ms = p->cfg->force_ptime;
+ conn->end.force_output_ptime = 1;
+ }
+
+ mgcp_rtp_end_config(endp, 0, &conn->end);
+
+ if (allocate_port(endp, conn) != 0) {
+ goto error2;
+ }
+
+ if (setup_rtp_processing(endp, conn) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x could not start RTP processing!\n",
+ ENDPOINT_NUMBER(endp));
+ goto error2;
+ }
+
+ /* policy CB */
+ if (p->cfg->policy_cb) {
+ int rc;
+ rc = p->cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp),
+ MGCP_ENDP_CRCX, p->trans);
+ switch (rc) {
+ case MGCP_POLICY_REJECT:
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "CRCX: endpoint:%x CRCX rejected by policy\n",
+ ENDPOINT_NUMBER(endp));
+ mgcp_release_endp(endp);
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ break;
+ case MGCP_POLICY_DEFER:
+ /* stop processing */
+ return NULL;
+ break;
+ case MGCP_POLICY_CONT:
+ /* just continue */
+ break;
+ }
+ }
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "CRCX: endpoint:%x Creating connection: CI: %u port: %u\n",
+ ENDPOINT_NUMBER(endp), conn->conn->id, conn->end.local_port);
+ if (p->cfg->change_cb)
+ p->cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX);
+
+ /* Send dummy packet, see also comments in mgcp_keepalive_timer_cb() */
+ OSMO_ASSERT(tcfg->keepalive_interval >= MGCP_KEEPALIVE_ONCE);
+ if (conn->conn->mode & MGCP_CONN_RECV_ONLY
+ && tcfg->keepalive_interval != MGCP_KEEPALIVE_NEVER)
+ send_dummy(endp, conn);
+
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "CRCX: endpoint:%x connection successfully created\n",
+ ENDPOINT_NUMBER(endp));
+ return create_response_with_sdp(endp, conn, "CRCX", p->trans);
+error2:
+ mgcp_release_endp(endp);
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "CRCX: endpoint:%x unable to create connection resource error\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, error_code, "CRCX", p->trans);
+}
+
+/* MDCX command handler, processes the received command */
+static struct msgb *handle_modify_con(struct mgcp_parse_data *p)
+{
+ struct mgcp_endpoint *endp = p->endp;
+ int error_code = 500;
+ int silent = 0;
+ int have_sdp = 0;
+ char *line;
+ const char *ci = NULL;
+ const char *local_options = NULL;
+ const char *mode = NULL;
+ struct mgcp_conn_rtp *conn = NULL;
+ uint32_t conn_id;
+
+ LOGP(DLMGCP, LOGL_NOTICE, "MDCX: modifying existing connection ...\n");
+
+ if (p->found != 0)
+ return create_err_response(NULL, 510, "MDCX", p->trans);
+
+ if (llist_count(&endp->conns) <= 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "MDCX: endpoint:%x endpoint is not holding a connection.\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "MDCX", p->trans);
+ }
+
+ for_each_line(line, p->save) {
+ if (!mgcp_check_param(endp, line))
+ continue;
+
+ switch (line[0]) {
+ case 'C':
+ if (mgcp_verify_call_id(endp, line + 3) != 0)
+ goto error3;
+ break;
+ case 'I':
+ ci = (const char *)line + 3;
+ if (mgcp_verify_ci(endp, ci) != 0)
+ goto error3;
+ break;
+ case 'L':
+ local_options = (const char *)line + 3;
+ break;
+ case 'M':
+ mode = (const char *)line + 3;
+ break;
+ case 'Z':
+ silent = strcmp("noanswer", line + 3) == 0;
+ break;
+ case '\0':
+ have_sdp = 1;
+ goto mgcp_header_done;
+ break;
+ default:
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "MDCX: endpoint:%x Unhandled MGCP option: '%c'/%d\n",
+ ENDPOINT_NUMBER(endp), line[0], line[0]);
+ break;
+ }
+ }
+
+mgcp_header_done:
+ if (mgcp_parse_ci(&conn_id, ci)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "MDCX: endpoint:%x insufficient parameters, missing ci (connectionIdentifier)\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "MDCX", p->trans);
+ }
+
+ conn = mgcp_conn_get_rtp(endp, conn_id);
+ if (!conn)
+ return create_err_response(endp, 400, "MDCX", p->trans);
+
+ if (mode) {
+ if (mgcp_parse_conn_mode(mode, endp, conn->conn) != 0) {
+ error_code = 517;
+ goto error3;
+ }
+ } else
+ conn->conn->mode = conn->conn->mode_orig;
+
+ if (have_sdp)
+ mgcp_parse_sdp_data(endp, conn, p);
+
+ set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
+ local_options);
+
+ if (!have_sdp && endp->local_options.codec)
+ mgcp_set_audio_info(p->cfg, &conn->end.codec,
+ PTYPE_UNDEFINED, endp->local_options.codec);
+
+ if (setup_rtp_processing(endp, conn) != 0)
+ goto error3;
+
+
+ /* policy CB */
+ if (p->cfg->policy_cb) {
+ int rc;
+ rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp),
+ MGCP_ENDP_MDCX, p->trans);
+ switch (rc) {
+ case MGCP_POLICY_REJECT:
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "MDCX: endpoint:%x rejected by policy\n",
+ ENDPOINT_NUMBER(endp));
+ if (silent)
+ goto out_silent;
+ return create_err_response(endp, 400, "MDCX", p->trans);
+ break;
+ case MGCP_POLICY_DEFER:
+ /* stop processing */
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "MDCX: endpoint:%x defered by policy\n",
+ ENDPOINT_NUMBER(endp));
+ return NULL;
+ break;
+ case MGCP_POLICY_CONT:
+ /* just continue */
+ break;
+ }
+ }
+
+ mgcp_rtp_end_config(endp, 1, &conn->end);
+
+ /* modify */
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "MDCX: endpoint:%x modified conn:%s\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn));
+ if (p->cfg->change_cb)
+ p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp),
+ MGCP_ENDP_MDCX);
+
+ /* Send dummy packet, see also comments in mgcp_keepalive_timer_cb() */
+ OSMO_ASSERT(endp->tcfg->keepalive_interval >= MGCP_KEEPALIVE_ONCE);
+ if (conn->conn->mode & MGCP_CONN_RECV_ONLY
+ && endp->tcfg->keepalive_interval != MGCP_KEEPALIVE_NEVER)
+ send_dummy(endp, conn);
+
+ if (silent)
+ goto out_silent;
+
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "MDCX: endpoint:%x connection successfully modified\n",
+ ENDPOINT_NUMBER(endp));
+ return create_response_with_sdp(endp, conn, "MDCX", p->trans);
+error3:
+ return create_err_response(endp, error_code, "MDCX", p->trans);
+
+out_silent:
+ LOGP(DLMGCP, LOGL_DEBUG, "MDCX: endpoint:%x silent exit\n",
+ ENDPOINT_NUMBER(endp));
+ return NULL;
+}
+
+/* DLCX command handler, processes the received command */
+static struct msgb *handle_delete_con(struct mgcp_parse_data *p)
+{
+ struct mgcp_endpoint *endp = p->endp;
+ int error_code = 400;
+ int silent = 0;
+ char *line;
+ char stats[1048];
+ const char *ci = NULL;
+ struct mgcp_conn_rtp *conn = NULL;
+ uint32_t conn_id;
+
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "DLCX: endpoint:%x deleting connection ...\n",
+ ENDPOINT_NUMBER(endp));
+
+ if (p->found != 0)
+ return create_err_response(NULL, error_code, "DLCX", p->trans);
+
+ if (llist_count(&endp->conns) <= 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "DLCX: endpoint:%x endpoint is not holding a connection.\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "DLCX", p->trans);
+ }
+
+ for_each_line(line, p->save) {
+ if (!mgcp_check_param(endp, line))
+ continue;
+
+ switch (line[0]) {
+ case 'C':
+ if (mgcp_verify_call_id(endp, line + 3) != 0)
+ goto error3;
+ break;
+ case 'I':
+ ci = (const char *)line + 3;
+ if (mgcp_verify_ci(endp, ci) != 0)
+ goto error3;
+ break;
+ case 'Z':
+ silent = strcmp("noanswer", line + 3) == 0;
+ break;
+ default:
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "DLCX: endpoint:%x Unhandled MGCP option: '%c'/%d\n",
+ ENDPOINT_NUMBER(endp), line[0], line[0]);
+ break;
+ }
+ }
+
+ /* policy CB */
+ if (p->cfg->policy_cb) {
+ int rc;
+ rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp),
+ MGCP_ENDP_DLCX, p->trans);
+ switch (rc) {
+ case MGCP_POLICY_REJECT:
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "DLCX: endpoint:%x rejected by policy\n",
+ ENDPOINT_NUMBER(endp));
+ if (silent)
+ goto out_silent;
+ return create_err_response(endp, 400, "DLCX", p->trans);
+ break;
+ case MGCP_POLICY_DEFER:
+ /* stop processing */
+ return NULL;
+ break;
+ case MGCP_POLICY_CONT:
+ /* just continue */
+ break;
+ }
+ }
+
+ /* When no connection id is supplied, we will interpret this as a
+ * wildcarded DLCX and drop all connections at once. (See also
+ * RFC3435 Section F.7) */
+ if (!ci) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "DLCX: endpoint:%x missing ci (connectionIdentifier), will remove all connections at once\n",
+ ENDPOINT_NUMBER(endp));
+
+ mgcp_release_endp(endp);
+
+ /* Note: In this case we do not return any statistics,
+ * as we assume that the client is not interested in
+ * this case. */
+ return create_ok_response(endp, 200, "DLCX", p->trans);
+ }
+
+ /* Parse the connection id */
+ if (mgcp_parse_ci(&conn_id, ci)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "DLCX: endpoint:%x insufficient parameters, invalid ci (connectionIdentifier)\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "DLCX", p->trans);
+ }
+
+ /* Find the connection */
+ conn = mgcp_conn_get_rtp(endp, conn_id);
+ if (!conn)
+ goto error3;
+
+ /* save the statistics of the current connection */
+ mgcp_format_stats(stats, sizeof(stats), conn->conn);
+
+ /* delete connection */
+ LOGP(DLMGCP, LOGL_DEBUG, "DLCX: endpoint:%x deleting conn:%s\n",
+ ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn));
+ mgcp_conn_free(endp, conn_id);
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "DLCX: endpoint:%x connection successfully deleted\n",
+ ENDPOINT_NUMBER(endp));
+
+ /* When all connections are closed, the endpoint will be released
+ * in order to be ready to be used by another call. */
+ if (llist_count(&endp->conns) <= 0) {
+ mgcp_release_endp(endp);
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "DLCX: endpoint:%x endpoint released\n",
+ ENDPOINT_NUMBER(endp));
+ }
+
+ if (p->cfg->change_cb)
+ p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp),
+ MGCP_ENDP_DLCX);
+
+ if (silent)
+ goto out_silent;
+ return create_ok_resp_with_param(endp, 250, "DLCX", p->trans, stats);
+
+error3:
+ return create_err_response(endp, error_code, "DLCX", p->trans);
+
+out_silent:
+ LOGP(DLMGCP, LOGL_DEBUG, "DLCX: endpoint:%x silent exit\n",
+ ENDPOINT_NUMBER(endp));
+ return NULL;
+}
+
+/* RSIP command handler, processes the received command */
+static struct msgb *handle_rsip(struct mgcp_parse_data *p)
+{
+ /* TODO: Also implement the resetting of a specific endpoint
+ * to make mgcp_send_reset_ep() work. Currently this will call
+ * mgcp_rsip_cb() in mgw_main.c, which sets reset_endpoints=1
+ * to make read_call_agent() reset all endpoints when called
+ * next time. In order to selectively reset endpoints some
+ * mechanism to distinguish which endpoint shall be resetted
+ * is needed */
+
+ LOGP(DLMGCP, LOGL_NOTICE, "RSIP: resetting all endpoints ...\n");
+
+ if (p->found != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "RSIP: failed to find the endpoint.\n");
+ return NULL;
+ }
+
+ if (p->cfg->reset_cb)
+ p->cfg->reset_cb(p->endp->tcfg);
+ return NULL;
+}
+
+static char extract_tone(const char *line)
+{
+ const char *str = strstr(line, "D/");
+ if (!str)
+ return CHAR_MAX;
+
+ return str[2];
+}
+
+/* This can request like DTMF detection and forward, fax detection... it
+ * can also request when the notification should be send and such. We don't
+ * do this right now. */
+static struct msgb *handle_noti_req(struct mgcp_parse_data *p)
+{
+ int res = 0;
+ char *line;
+ char tone = CHAR_MAX;
+
+ LOGP(DLMGCP, LOGL_NOTICE, "RQNT: processing request for notification ...\n");
+
+ if (p->found != 0)
+ return create_err_response(NULL, 400, "RQNT", p->trans);
+
+ for_each_line(line, p->save) {
+ switch (line[0]) {
+ case 'S':
+ tone = extract_tone(line);
+ break;
+ }
+ }
+
+ /* we didn't see a signal request with a tone */
+ if (tone == CHAR_MAX)
+ return create_ok_response(p->endp, 200, "RQNT", p->trans);
+
+ if (p->cfg->rqnt_cb)
+ res = p->cfg->rqnt_cb(p->endp, tone);
+
+ return res == 0 ?
+ create_ok_response(p->endp, 200, "RQNT", p->trans) :
+ create_err_response(p->endp, res, "RQNT", p->trans);
+}
+
+/* Connection keepalive timer, will take care that dummy packets are send
+ * regulary, so that NAT connections stay open */
+static void mgcp_keepalive_timer_cb(void *_tcfg)
+{
+ struct mgcp_trunk_config *tcfg = _tcfg;
+ struct mgcp_conn *conn;
+ int i;
+
+ LOGP(DLMGCP, LOGL_DEBUG, "triggered trunk %d keepalive timer\n",
+ tcfg->trunk_nr);
+
+ /* Do not accept invalid configuration values
+ * valid is MGCP_KEEPALIVE_NEVER, MGCP_KEEPALIVE_ONCE and
+ * values greater 0 */
+ OSMO_ASSERT(tcfg->keepalive_interval >= MGCP_KEEPALIVE_ONCE);
+
+ /* The dummy packet functionality has been disabled, we will exit
+ * immediately, no further timer is scheduled, which means we will no
+ * longer send dummy packets even when we did before */
+ if (tcfg->keepalive_interval == MGCP_KEEPALIVE_NEVER)
+ return;
+
+ /* In cases where only one dummy packet is sent, we do not need
+ * the timer since the functions that handle the CRCX and MDCX are
+ * triggering the sending of the dummy packet. So we behave like in
+ * the MGCP_KEEPALIVE_NEVER case */
+ if (tcfg->keepalive_interval == MGCP_KEEPALIVE_ONCE)
+ return;
+
+ /* Send walk over all endpoints and send out dummy packets through
+ * every connection present on each endpoint */
+ for (i = 1; i < tcfg->number_endpoints; ++i) {
+ struct mgcp_endpoint *endp = &tcfg->endpoints[i];
+ llist_for_each_entry(conn, &endp->conns, entry) {
+ if (conn->mode == MGCP_CONN_RECV_ONLY)
+ send_dummy(endp, &conn->u.rtp);
+ }
+ }
+
+ /* Schedule the keepalive timer for the next round */
+ LOGP(DLMGCP, LOGL_DEBUG, "rescheduling trunk %d keepalive timer\n",
+ tcfg->trunk_nr);
+ osmo_timer_schedule(&tcfg->keepalive_timer, tcfg->keepalive_interval,
+ 0);
+}
+
+void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval)
+{
+ tcfg->keepalive_interval = interval;
+ osmo_timer_setup(&tcfg->keepalive_timer, mgcp_keepalive_timer_cb, tcfg);
+
+ if (interval <= 0)
+ osmo_timer_del(&tcfg->keepalive_timer);
+ else
+ osmo_timer_schedule(&tcfg->keepalive_timer,
+ tcfg->keepalive_interval, 0);
+}
+
+/*! allocate configuration with default values.
+ * (called once at startup by main function) */
+struct mgcp_config *mgcp_config_alloc(void)
+{
+ struct mgcp_config *cfg;
+
+ cfg = talloc_zero(NULL, struct mgcp_config);
+ if (!cfg) {
+ LOGP(DLMGCP, LOGL_FATAL, "Failed to allocate config.\n");
+ return NULL;
+ }
+
+ cfg->net_ports.range_start = RTP_PORT_DEFAULT_RANGE_START;
+ cfg->net_ports.range_end = RTP_PORT_DEFAULT_RANGE_END;
+ cfg->net_ports.last_port = cfg->net_ports.range_start;
+
+ cfg->source_port = 2427;
+ cfg->source_addr = talloc_strdup(cfg, "0.0.0.0");
+ cfg->osmux_addr = talloc_strdup(cfg, "0.0.0.0");
+
+ cfg->rtp_processing_cb = &mgcp_rtp_processing_default;
+ cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default;
+
+ cfg->get_net_downlink_format_cb = &mgcp_get_net_downlink_format_default;
+
+ /* default trunk handling */
+ cfg->trunk.cfg = cfg;
+ cfg->trunk.trunk_nr = 0;
+ cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL;
+ cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000");
+ cfg->trunk.audio_payload = 126;
+ cfg->trunk.audio_send_ptime = 1;
+ cfg->trunk.audio_send_name = 1;
+ cfg->trunk.omit_rtcp = 0;
+ mgcp_trunk_set_keepalive(&cfg->trunk, MGCP_KEEPALIVE_ONCE);
+
+ INIT_LLIST_HEAD(&cfg->trunks);
+
+ return cfg;
+}
+
+/*! allocate configuration with default values.
+ * (called once at startup by VTY)
+ * \param[in] cfg mgcp configuration
+ * \param[in] nr trunk number
+ * \returns pointer to allocated trunk configuration */
+struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr)
+{
+ struct mgcp_trunk_config *trunk;
+
+ trunk = talloc_zero(cfg, struct mgcp_trunk_config);
+ if (!trunk) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to allocate.\n");
+ return NULL;
+ }
+
+ trunk->cfg = cfg;
+ trunk->trunk_type = MGCP_TRUNK_E1;
+ trunk->trunk_nr = nr;
+ trunk->audio_name = talloc_strdup(cfg, "AMR/8000");
+ trunk->audio_payload = 126;
+ trunk->audio_send_ptime = 1;
+ trunk->audio_send_name = 1;
+ trunk->number_endpoints = 33;
+ trunk->omit_rtcp = 0;
+ mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
+ llist_add_tail(&trunk->entry, &cfg->trunks);
+ return trunk;
+}
+
+/*! get trunk configuration by trunk number (index).
+ * \param[in] cfg mgcp configuration
+ * \param[in] index trunk number
+ * \returns pointer to trunk configuration, NULL on error */
+struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index)
+{
+ struct mgcp_trunk_config *trunk;
+
+ llist_for_each_entry(trunk, &cfg->trunks, entry)
+ if (trunk->trunk_nr == index)
+ return trunk;
+
+ return NULL;
+}
+
+/*! allocate endpoints and set default values.
+ * (called once at startup by VTY)
+ * \param[in] tcfg trunk configuration
+ * \returns 0 on success, -1 on failure */
+int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg)
+{
+ int i;
+
+ tcfg->endpoints = _talloc_zero_array(tcfg->cfg,
+ sizeof(struct mgcp_endpoint),
+ tcfg->number_endpoints,
+ "endpoints");
+ if (!tcfg->endpoints)
+ return -1;
+
+ for (i = 0; i < tcfg->number_endpoints; ++i) {
+ INIT_LLIST_HEAD(&tcfg->endpoints[i].conns);
+ tcfg->endpoints[i].cfg = tcfg->cfg;
+ tcfg->endpoints[i].tcfg = tcfg;
+
+ /* NOTE: Currently all endpoints are of type RTP, this will
+ * change when new variations are implemented */
+ tcfg->endpoints[i].type = &ep_typeset.rtp;
+ }
+
+ return 0;
+}
+
+/*! relase endpoint, all open connections are closed.
+ * \param[in] endp endpoint to release */
+void mgcp_release_endp(struct mgcp_endpoint *endp)
+{
+ LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint:%x\n",
+ ENDPOINT_NUMBER(endp));
+
+ /* Normally this function should only be called wehen
+ * all connections have been removed already. In case
+ * that there are still connections open (e.g. when
+ * RSIP is executed), free them all at once. */
+ mgcp_conn_free_all(endp);
+
+ /* Reset endpoint parameters and states */
+ talloc_free(endp->callid);
+ endp->callid = NULL;
+ talloc_free(endp->local_options.string);
+ endp->local_options.string = NULL;
+ talloc_free(endp->local_options.codec);
+ endp->local_options.codec = NULL;
+}
+
+static int send_agent(struct mgcp_config *cfg, const char *buf, int len)
+{
+ return write(cfg->gw_fd.bfd.fd, buf, len);
+}
+
+/*! Reset all endpoints by sending RSIP message to self.
+ * (called by VTY)
+ * \param[in] endp trunk endpoint
+ * \param[in] endpoint number
+ * \returns 0 on success, -1 on error */
+int mgcp_send_reset_all(struct mgcp_config *cfg)
+{
+ int rc;
+
+ static const char mgcp_reset[] = {
+ "RSIP 1 *@mgw MGCP 1.0\r\n"
+ };
+
+ rc = send_agent(cfg, mgcp_reset, sizeof mgcp_reset - 1);
+ if (rc <= 0)
+ return -1;
+
+ return 0;
+}
+
+/*! Reset a single endpoint by sending RSIP message to self.
+ * (called by VTY)
+ * \param[in] endp trunk endpoint
+ * \param[in] endpoint number
+ * \returns 0 on success, -1 on error */
+int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint)
+{
+ char buf[128];
+ int len;
+ int rc;
+
+ len = snprintf(buf, sizeof(buf),
+ "RSIP 39 %x@mgw MGCP 1.0\r\n", endpoint);
+ if (len < 0)
+ return -1;
+
+ buf[sizeof(buf) - 1] = '\0';
+
+ rc = send_agent(endp->cfg, buf, len);
+ if (rc <= 0)
+ return -1;
+
+ return 0;
+}
diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c
new file mode 100644
index 0000000..f45d6e7
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_sdp.c
@@ -0,0 +1,409 @@
+/*
+ * Some SDP file parsing...
+ *
+ * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2014 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 <osmocom/core/msgb.h>
+#include <osmocom/mgcp/mgcp.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_msg.h>
+
+#include <errno.h>
+
+struct sdp_rtp_map {
+ /* the type */
+ int payload_type;
+ /* null, static or later dynamic codec name */
+ char *codec_name;
+ /* A pointer to the original line for later parsing */
+ char *map_line;
+
+ int rate;
+ int channels;
+};
+
+/*! Set codec configuration depending on payload type and codec name.
+ * \param[in] ctx talloc context.
+ * \param[out] codec configuration (caller provided memory).
+ * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined).
+ * \param[in] audio_name audio codec name (e.g. "GSM/8000/1").
+ * \returns 0 on success, -1 on failure. */
+int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec,
+ int payload_type, const char *audio_name)
+{
+ int rate = codec->rate;
+ int channels = codec->channels;
+ char audio_codec[64];
+
+ talloc_free(codec->subtype_name);
+ codec->subtype_name = NULL;
+ talloc_free(codec->audio_name);
+ codec->audio_name = NULL;
+
+ if (payload_type != PTYPE_UNDEFINED)
+ codec->payload_type = payload_type;
+
+ if (!audio_name) {
+ switch (payload_type) {
+ case 0:
+ audio_name = "PCMU/8000/1";
+ break;
+ case 3:
+ audio_name = "GSM/8000/1";
+ break;
+ case 8:
+ audio_name = "PCMA/8000/1";
+ break;
+ case 18:
+ audio_name = "G729/8000/1";
+ break;
+ default:
+ /* Payload type is unknown, don't change rate and
+ * channels. */
+ /* TODO: return value? */
+ return 0;
+ }
+ }
+
+ if (sscanf(audio_name, "%63[^/]/%d/%d",
+ audio_codec, &rate, &channels) < 1)
+ return -EINVAL;
+
+ codec->rate = rate;
+ codec->channels = channels;
+ codec->subtype_name = talloc_strdup(ctx, audio_codec);
+ codec->audio_name = talloc_strdup(ctx, audio_name);
+
+ if (!strcmp(audio_codec, "G729")) {
+ codec->frame_duration_num = 10;
+ codec->frame_duration_den = 1000;
+ } else {
+ codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+ codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+ }
+
+ if (payload_type < 0) {
+ payload_type = 96;
+ if (rate == 8000 && channels == 1) {
+ if (!strcmp(audio_codec, "GSM"))
+ payload_type = 3;
+ else if (!strcmp(audio_codec, "PCMA"))
+ payload_type = 8;
+ else if (!strcmp(audio_codec, "PCMU"))
+ payload_type = 0;
+ else if (!strcmp(audio_codec, "G729"))
+ payload_type = 18;
+ }
+
+ codec->payload_type = payload_type;
+ }
+
+ if (channels != 1)
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Channels != 1 in SDP: '%s'\n", audio_name);
+
+ return 0;
+}
+
+static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used)
+{
+ int i;
+
+ for (i = 0; i < used; ++i) {
+ switch (codecs[i].payload_type) {
+ case 0:
+ codecs[i].codec_name = "PCMU";
+ codecs[i].rate = 8000;
+ codecs[i].channels = 1;
+ break;
+ case 3:
+ codecs[i].codec_name = "GSM";
+ codecs[i].rate = 8000;
+ codecs[i].channels = 1;
+ break;
+ case 8:
+ codecs[i].codec_name = "PCMA";
+ codecs[i].rate = 8000;
+ codecs[i].channels = 1;
+ break;
+ case 18:
+ codecs[i].codec_name = "G729";
+ codecs[i].rate = 8000;
+ codecs[i].channels = 1;
+ break;
+ }
+ }
+}
+
+static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used,
+ int payload, const char *audio_name)
+{
+ int i;
+
+ for (i = 0; i < used; ++i) {
+ char audio_codec[64];
+ int rate = -1;
+ int channels = -1;
+ if (codecs[i].payload_type != payload)
+ continue;
+ if (sscanf(audio_name, "%63[^/]/%d/%d",
+ audio_codec, &rate, &channels) < 1) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to parse '%s'\n",
+ audio_name);
+ continue;
+ }
+
+ codecs[i].map_line = talloc_strdup(ctx, audio_name);
+ codecs[i].codec_name = talloc_strdup(ctx, audio_codec);
+ codecs[i].rate = rate;
+ codecs[i].channels = channels;
+ return;
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload,
+ audio_name);
+}
+
+/* Check if the codec matches what is set up in the trunk config */
+static int is_codec_compatible(const struct mgcp_endpoint *endp,
+ const struct sdp_rtp_map *codec)
+{
+ char *codec_str;
+ char audio_codec[64];
+
+ if (!codec->codec_name)
+ return 0;
+
+ /* GSM, GSM/8000 and GSM/8000/1 should all be compatible...
+ * let's go by name first. */
+ codec_str = endp->tcfg->audio_name;
+ if (sscanf(codec_str, "%63[^/]/%*d/%*d", audio_codec) < 1)
+ return 0;
+
+ return strcasecmp(audio_codec, codec->codec_name) == 0;
+}
+
+/*! Analyze SDP input string.
+ * \param[in] endp trunk endpoint.
+ * \param[out] conn associated rtp connection.
+ * \param[out] caller provided memory to store the parsing results.
+ * \returns 0 on success, -1 on failure.
+ *
+ * Note: In conn (conn->end) the function returns the packet duration,
+ * the rtp port and the rtcp port */
+int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp,
+ struct mgcp_conn_rtp *conn,
+ struct mgcp_parse_data *p)
+{
+ struct sdp_rtp_map codecs[10];
+ int codecs_used = 0;
+ char *line;
+ int maxptime = -1;
+ int i;
+ int codecs_assigned = 0;
+ void *tmp_ctx = talloc_new(NULL);
+ struct mgcp_rtp_end *rtp;
+
+ int payload;
+ int ptime, ptime2 = 0;
+ char audio_name[64];
+ int port, rc;
+ char ipv4[16];
+
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(p);
+
+ rtp = &conn->end;
+ memset(&codecs, 0, sizeof(codecs));
+
+ for_each_line(line, p->save) {
+ switch (line[0]) {
+ case 'o':
+ case 's':
+ case 't':
+ case 'v':
+ /* skip these SDP attributes */
+ break;
+ case 'a':
+ if (sscanf(line, "a=rtpmap:%d %63s",
+ &payload, audio_name) == 2) {
+ codecs_update(tmp_ctx, codecs,
+ codecs_used, payload, audio_name);
+ } else
+ if (sscanf
+ (line, "a=ptime:%d-%d", &ptime, &ptime2) >= 1) {
+ if (ptime2 > 0 && ptime2 != ptime)
+ rtp->packet_duration_ms = 0;
+ else
+ rtp->packet_duration_ms = ptime;
+ } else if (sscanf(line, "a=maxptime:%d", &ptime2)
+ == 1) {
+ maxptime = ptime2;
+ }
+ break;
+ case 'm':
+ rc = sscanf(line,
+ "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d",
+ &port, &codecs[0].payload_type,
+ &codecs[1].payload_type,
+ &codecs[2].payload_type,
+ &codecs[3].payload_type,
+ &codecs[4].payload_type,
+ &codecs[5].payload_type,
+ &codecs[6].payload_type,
+ &codecs[7].payload_type,
+ &codecs[8].payload_type,
+ &codecs[9].payload_type);
+ if (rc >= 2) {
+ rtp->rtp_port = htons(port);
+ rtp->rtcp_port = htons(port + 1);
+ codecs_used = rc - 1;
+ codecs_initialize(tmp_ctx, codecs, codecs_used);
+ }
+ break;
+ case 'c':
+
+ if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) {
+ inet_aton(ipv4, &rtp->addr);
+ }
+ break;
+ default:
+ if (p->endp)
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Unhandled SDP option: '%c'/%d on 0x%x\n",
+ line[0], line[0],
+ ENDPOINT_NUMBER(p->endp));
+ else
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Unhandled SDP option: '%c'/%d\n",
+ line[0], line[0]);
+ break;
+ }
+ }
+
+ /* Now select the primary and alt_codec */
+ for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) {
+ struct mgcp_rtp_codec *codec = codecs_assigned == 0 ?
+ &rtp->codec : &rtp->alt_codec;
+
+ if (endp->tcfg->no_audio_transcoding &&
+ !is_codec_compatible(endp, &codecs[i])) {
+ LOGP(DLMGCP, LOGL_NOTICE, "Skipping codec %s\n",
+ codecs[i].codec_name);
+ continue;
+ }
+
+ mgcp_set_audio_info(p->cfg, codec,
+ codecs[i].payload_type, codecs[i].map_line);
+ codecs_assigned += 1;
+ }
+
+ if (codecs_assigned > 0) {
+ /* TODO/XXX: Store this per codec and derive it on use */
+ if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den >
+ rtp->codec.frame_duration_num * 1500) {
+ /* more than 1 frame */
+ rtp->packet_duration_ms = 0;
+ }
+
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Got media info via SDP: port %d, payload %d (%s), "
+ "duration %d, addr %s\n",
+ ntohs(rtp->rtp_port), rtp->codec.payload_type,
+ rtp->codec.subtype_name ? rtp->
+ codec.subtype_name : "unknown", rtp->packet_duration_ms,
+ inet_ntoa(rtp->addr));
+ }
+
+ talloc_free(tmp_ctx);
+ return codecs_assigned > 0;
+}
+
+/*! Generate SDP response string.
+ * \param[in] endp trunk endpoint.
+ * \param[in] conn associated rtp connection.
+ * \param[out] sdp msg buffer to append resulting SDP string data.
+ * \param[in] addr IPV4 address string (e.g. 192.168.100.1).
+ * \returns 0 on success, -1 on failure. */
+int mgcp_write_response_sdp(const struct mgcp_endpoint *endp,
+ const struct mgcp_conn_rtp *conn, struct msgb *sdp,
+ const char *addr)
+{
+ const char *fmtp_extra;
+ const char *audio_name;
+ int payload_type;
+ int rc;
+
+ OSMO_ASSERT(endp);
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(sdp);
+ OSMO_ASSERT(addr);
+
+ /* FIXME: constify endp and conn args in get_net_donwlink_format_cb() */
+ endp->cfg->get_net_downlink_format_cb((struct mgcp_endpoint *)endp,
+ &payload_type, &audio_name,
+ &fmtp_extra,
+ (struct mgcp_conn_rtp *)conn);
+
+ rc = msgb_printf(sdp,
+ "v=0\r\n"
+ "o=- %u 23 IN IP4 %s\r\n"
+ "s=-\r\n"
+ "c=IN IP4 %s\r\n"
+ "t=0 0\r\n", conn->conn->id, addr, addr);
+
+ if (rc < 0)
+ goto buffer_too_small;
+
+ if (payload_type >= 0) {
+ rc = msgb_printf(sdp, "m=audio %d RTP/AVP %d\r\n",
+ conn->end.local_port, payload_type);
+ if (rc < 0)
+ goto buffer_too_small;
+
+ if (audio_name && endp->tcfg->audio_send_name) {
+ rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n",
+ payload_type, audio_name);
+
+ if (rc < 0)
+ goto buffer_too_small;
+ }
+
+ if (fmtp_extra) {
+ rc = msgb_printf(sdp, "%s\r\n", fmtp_extra);
+
+ if (rc < 0)
+ goto buffer_too_small;
+ }
+ }
+ if (conn->end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) {
+ rc = msgb_printf(sdp, "a=ptime:%u\r\n",
+ conn->end.packet_duration_ms);
+ if (rc < 0)
+ goto buffer_too_small;
+ }
+
+ return 0;
+
+buffer_too_small:
+ LOGP(DLMGCP, LOGL_ERROR, "SDP messagebuffer too small\n");
+ return -1;
+}
diff --git a/src/libosmo-mgcp/mgcp_stat.c b/src/libosmo-mgcp/mgcp_stat.c
new file mode 100644
index 0000000..b84f5f2
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_stat.c
@@ -0,0 +1,128 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The statistics generator */
+
+/*
+ * (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2012 by On-Waves
+ * (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <osmocom/mgcp/mgcp_stat.h>
+#include <limits.h>
+
+/* Helper function for mgcp_format_stats_rtp() to calculate packet loss */
+void calc_loss(struct mgcp_rtp_state *state,
+ struct mgcp_rtp_end *end, uint32_t *expected,
+ int *loss)
+{
+ *expected = state->stats_cycles + state->stats_max_seq;
+ *expected = *expected - state->stats_base_seq + 1;
+
+ if (!state->stats_initialized) {
+ *expected = 0;
+ *loss = 0;
+ return;
+ }
+
+ /*
+ * Make sure the sign is correct and use the biggest
+ * positive/negative number that fits.
+ */
+ *loss = *expected - end->packets_rx;
+ if (*expected < end->packets_rx) {
+ if (*loss > 0)
+ *loss = INT_MIN;
+ } else {
+ if (*loss < 0)
+ *loss = INT_MAX;
+ }
+}
+
+/* Helper function for mgcp_format_stats_rtp() to calculate jitter */
+uint32_t calc_jitter(struct mgcp_rtp_state *state)
+{
+ if (!state->stats_initialized)
+ return 0;
+ return state->stats_jitter >> 4;
+}
+
+/* Generate statistics for an RTP connection */
+static void mgcp_format_stats_rtp(char *str, size_t str_len,
+ struct mgcp_conn_rtp *conn)
+{
+ uint32_t expected, jitter;
+ int ploss;
+ int nchars;
+
+ calc_loss(&conn->state, &conn->end, &expected, &ploss);
+ jitter = calc_jitter(&conn->state);
+
+ nchars = snprintf(str, str_len,
+ "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u",
+ conn->end.packets_tx, conn->end.octets_tx,
+ conn->end.packets_rx, conn->end.octets_rx,
+ ploss, jitter);
+ if (nchars < 0 || nchars >= str_len)
+ goto truncate;
+
+ str += nchars;
+ str_len -= nchars;
+
+ /* Error Counter */
+ nchars = snprintf(str, str_len,
+ "\r\nX-Osmo-CP: EC TI=%u, TO=%u",
+ conn->state.in_stream.err_ts_counter,
+ conn->state.out_stream.err_ts_counter);
+ if (nchars < 0 || nchars >= str_len)
+ goto truncate;
+
+ str += nchars;
+ str_len -= nchars;
+
+ if (conn->osmux.state == OSMUX_STATE_ENABLED) {
+ snprintf(str, str_len,
+ "\r\nX-Osmux-ST: CR=%u, BR=%u",
+ conn->osmux.stats.chunks, conn->osmux.stats.octets);
+ }
+
+truncate:
+ str[str_len - 1] = '\0';
+}
+
+/*! format statistics into an mgcp parameter string.
+ * \param[out] str resulting string
+ * \param[in] str_len length of the string buffer
+ * \param[in] conn connection to evaluate */
+void mgcp_format_stats(char *str, size_t str_len, struct mgcp_conn *conn)
+{
+ memset(str, 0, str_len);
+ if (!conn)
+ return;
+
+ /* NOTE: At the moment we only support generating statistics for
+ * RTP connections. However, in the future we may also want to
+ * generate statistics for other connection types as well. Lets
+ * keep this option open: */
+ switch (conn->type) {
+ case MGCP_CONN_TYPE_RTP:
+ mgcp_format_stats_rtp(str, str_len, &conn->u.rtp);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c
new file mode 100644
index 0000000..7ff1fdd
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_vty.c
@@ -0,0 +1,1306 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 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 <osmocom/core/talloc.h>
+#include <osmocom/mgcp/mgcp.h>
+#include <osmocom/mgcp/mgcp_common.h>
+#include <osmocom/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/vty.h>
+#include <osmocom/mgcp/mgcp_conn.h>
+
+#include <string.h>
+
+#define RTCP_OMIT_STR "Drop RTCP packets in both directions\n"
+#define RTP_PATCH_STR "Modify RTP packet header in both directions\n"
+#define RTP_KEEPALIVE_STR "Send dummy UDP packet to net RTP destination\n"
+
+static struct mgcp_config *g_cfg = NULL;
+
+static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr)
+{
+ struct mgcp_trunk_config *trunk;
+
+ if (nr == 0)
+ trunk = &cfg->trunk;
+ else
+ trunk = mgcp_trunk_num(cfg, nr);
+
+ return trunk;
+}
+
+struct cmd_node mgcp_node = {
+ MGCP_NODE,
+ "%s(config-mgcp)# ",
+ 1,
+};
+
+struct cmd_node trunk_node = {
+ TRUNK_NODE,
+ "%s(config-mgcp-trunk)# ",
+ 1,
+};
+
+static int config_write_mgcp(struct vty *vty)
+{
+ vty_out(vty, "mgcp%s", VTY_NEWLINE);
+ if (g_cfg->local_ip)
+ vty_out(vty, " local ip %s%s", g_cfg->local_ip, VTY_NEWLINE);
+ vty_out(vty, " bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE);
+ vty_out(vty, " bind port %u%s", g_cfg->source_port, VTY_NEWLINE);
+ vty_out(vty, " rtp net-range %u %u%s",
+ g_cfg->net_ports.range_start, g_cfg->net_ports.range_end,
+ VTY_NEWLINE);
+ if (g_cfg->net_ports.bind_addr)
+ vty_out(vty, " rtp net-bind-ip %s%s",
+ g_cfg->net_ports.bind_addr, VTY_NEWLINE);
+ if (g_cfg->net_ports.bind_addr_probe)
+ vty_out(vty, " rtp ip-probing%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " no rtp ip-probing%s", VTY_NEWLINE);
+ vty_out(vty, " rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE);
+ if (g_cfg->trunk.keepalive_interval == MGCP_KEEPALIVE_ONCE)
+ vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE);
+ else if (g_cfg->trunk.keepalive_interval)
+ vty_out(vty, " rtp keep-alive %d%s",
+ g_cfg->trunk.keepalive_interval, VTY_NEWLINE);
+ else
+ vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE);
+
+ if (g_cfg->trunk.omit_rtcp)
+ vty_out(vty, " rtcp-omit%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE);
+ if (g_cfg->trunk.force_constant_ssrc
+ || g_cfg->trunk.force_aligned_timing) {
+ vty_out(vty, " %srtp-patch ssrc%s",
+ g_cfg->trunk.force_constant_ssrc ? "" : "no ",
+ VTY_NEWLINE);
+ vty_out(vty, " %srtp-patch timestamp%s",
+ g_cfg->trunk.force_aligned_timing ? "" : "no ",
+ VTY_NEWLINE);
+ } else
+ vty_out(vty, " no rtp-patch%s", VTY_NEWLINE);
+ if (g_cfg->trunk.audio_payload != -1)
+ vty_out(vty, " sdp audio-payload number %d%s",
+ g_cfg->trunk.audio_payload, VTY_NEWLINE);
+ if (g_cfg->trunk.audio_name)
+ vty_out(vty, " sdp audio-payload name %s%s",
+ g_cfg->trunk.audio_name, VTY_NEWLINE);
+ if (g_cfg->trunk.audio_fmtp_extra)
+ vty_out(vty, " sdp audio fmtp-extra %s%s",
+ g_cfg->trunk.audio_fmtp_extra, VTY_NEWLINE);
+ vty_out(vty, " %ssdp audio-payload send-ptime%s",
+ g_cfg->trunk.audio_send_ptime ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %ssdp audio-payload send-name%s",
+ g_cfg->trunk.audio_send_name ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " loop %u%s", ! !g_cfg->trunk.audio_loop, VTY_NEWLINE);
+ vty_out(vty, " number endpoints %u%s",
+ g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE);
+ vty_out(vty, " %sallow-transcoding%s",
+ g_cfg->trunk.no_audio_transcoding ? "no " : "", VTY_NEWLINE);
+ if (g_cfg->call_agent_addr)
+ vty_out(vty, " call-agent ip %s%s", g_cfg->call_agent_addr,
+ VTY_NEWLINE);
+ if (g_cfg->force_ptime > 0)
+ vty_out(vty, " rtp force-ptime %d%s", g_cfg->force_ptime,
+ VTY_NEWLINE);
+
+ switch (g_cfg->osmux) {
+ case OSMUX_USAGE_ON:
+ vty_out(vty, " osmux on%s", VTY_NEWLINE);
+ break;
+ case OSMUX_USAGE_ONLY:
+ vty_out(vty, " osmux only%s", VTY_NEWLINE);
+ break;
+ case OSMUX_USAGE_OFF:
+ default:
+ vty_out(vty, " osmux off%s", VTY_NEWLINE);
+ break;
+ }
+ if (g_cfg->osmux) {
+ vty_out(vty, " osmux bind-ip %s%s",
+ g_cfg->osmux_addr, VTY_NEWLINE);
+ vty_out(vty, " osmux batch-factor %d%s",
+ g_cfg->osmux_batch, VTY_NEWLINE);
+ vty_out(vty, " osmux batch-size %u%s",
+ g_cfg->osmux_batch_size, VTY_NEWLINE);
+ vty_out(vty, " osmux port %u%s",
+ g_cfg->osmux_port, VTY_NEWLINE);
+ vty_out(vty, " osmux dummy %s%s",
+ g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+static void dump_rtp_end(struct vty *vty, struct mgcp_rtp_state *state,
+ struct mgcp_rtp_end *end)
+{
+ struct mgcp_rtp_codec *codec = &end->codec;
+
+ vty_out(vty,
+ " Timestamp Errs: %d->%d%s"
+ " Dropped Packets: %d%s"
+ " Payload Type: %d Rate: %u Channels: %d %s"
+ " Frame Duration: %u Frame Denominator: %u%s"
+ " FPP: %d Packet Duration: %u%s"
+ " FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s"
+ " Output-Enabled: %d Force-PTIME: %d%s",
+ state->in_stream.err_ts_counter,
+ state->out_stream.err_ts_counter, VTY_NEWLINE,
+ end->dropped_packets, VTY_NEWLINE,
+ codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE,
+ codec->frame_duration_num, codec->frame_duration_den,
+ VTY_NEWLINE, end->frames_per_packet, end->packet_duration_ms,
+ VTY_NEWLINE, end->fmtp_extra, codec->audio_name,
+ codec->subtype_name, VTY_NEWLINE, end->output_enabled,
+ end->force_output_ptime, VTY_NEWLINE);
+}
+
+static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg,
+ int verbose)
+{
+ int i;
+ struct mgcp_conn *conn;
+
+ vty_out(vty, "%s trunk nr %d with %d endpoints:%s",
+ cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1",
+ cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE);
+
+ if (!cfg->endpoints) {
+ vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE);
+ return;
+ }
+
+ for (i = 1; i < cfg->number_endpoints; ++i) {
+ struct mgcp_endpoint *endp = &cfg->endpoints[i];
+
+ vty_out(vty, "Endpoint 0x%.2x:%s", i, VTY_NEWLINE);
+
+ llist_for_each_entry(conn, &endp->conns, entry) {
+ vty_out(vty, " CONN: %s%s",
+ mgcp_conn_dump(conn), VTY_NEWLINE);
+
+ if (verbose) {
+ /* FIXME: Also add verbosity for other
+ * connection types (E1) as soon as
+ * the implementation is available */
+ if (conn->type == MGCP_CONN_TYPE_RTP) {
+ dump_rtp_end(vty, &conn->u.rtp.state,
+ &conn->u.rtp.end);
+ }
+ }
+ }
+ }
+}
+
+DEFUN(show_mcgp, show_mgcp_cmd,
+ "show mgcp [stats]",
+ SHOW_STR
+ "Display information about the MGCP Media Gateway\n"
+ "Include Statistics\n")
+{
+ struct mgcp_trunk_config *trunk;
+ int show_stats = argc >= 1;
+
+ dump_trunk(vty, &g_cfg->trunk, show_stats);
+
+ llist_for_each_entry(trunk, &g_cfg->trunks, entry)
+ dump_trunk(vty, trunk, show_stats);
+
+ if (g_cfg->osmux)
+ vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(),
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp, cfg_mgcp_cmd, "mgcp", "Configure the MGCP")
+{
+ vty->node = MGCP_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_local_ip,
+ cfg_mgcp_local_ip_cmd,
+ "local ip A.B.C.D",
+ "Local options for the SDP record\n"
+ IP_STR "IPv4 Address to use in SDP record\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define BIND_STR "Listen/Bind related socket option\n"
+DEFUN(cfg_mgcp_bind_ip,
+ cfg_mgcp_bind_ip_cmd,
+ "bind ip A.B.C.D", BIND_STR IP_STR "IPv4 Address to bind to\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_port,
+ cfg_mgcp_bind_port_cmd,
+ "bind port <0-65534>",
+ BIND_STR "Port information\n" "UDP port to listen for MGCP messages\n")
+{
+ unsigned int port = atoi(argv[0]);
+ g_cfg->source_port = port;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_early,
+ cfg_mgcp_bind_early_cmd,
+ "bind early (0|1)",
+ BIND_STR
+ "Bind local ports on start up\n" "Bind on demand\n" "Bind on startup\n")
+{
+ vty_out(vty, "bind early is deprecated, remove it from the config.\n");
+ return CMD_WARNING;
+}
+
+static void parse_range(struct mgcp_port_range *range, const char **argv)
+{
+ range->range_start = atoi(argv[0]);
+ range->range_end = atoi(argv[1]);
+ range->last_port = g_cfg->net_ports.range_start;
+}
+
+#define RTP_STR "RTP configuration\n"
+#define UDP_PORT_STR "UDP Port number\n"
+#define NET_START_STR "First UDP port allocated\n"
+#define RANGE_START_STR "Start of the range of ports\n"
+#define RANGE_END_STR "End of the range of ports\n"
+
+DEFUN(cfg_mgcp_rtp_net_range,
+ cfg_mgcp_rtp_net_range_cmd,
+ "rtp net-range <0-65534> <0-65534>",
+ RTP_STR "Range of ports to use for the NET side\n"
+ RANGE_START_STR RANGE_END_STR)
+{
+ parse_range(&g_cfg->net_ports, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_bind_ip,
+ cfg_mgcp_rtp_net_bind_ip_cmd,
+ "rtp net-bind-ip A.B.C.D",
+ RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->net_ports.bind_addr, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_no_net_bind_ip,
+ cfg_mgcp_rtp_no_net_bind_ip_cmd,
+ "no rtp net-bind-ip",
+ NO_STR RTP_STR "Bind endpoints facing the Network\n"
+ "Address to bind to\n")
+{
+ talloc_free(g_cfg->net_ports.bind_addr);
+ g_cfg->net_ports.bind_addr = NULL;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_bind_ip_probing,
+ cfg_mgcp_rtp_net_bind_ip_probing_cmd,
+ "rtp ip-probing",
+ RTP_STR "automatic rtp bind ip selection\n")
+{
+ g_cfg->net_ports.bind_addr_probe = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_no_net_bind_ip_probing,
+ cfg_mgcp_rtp_no_net_bind_ip_probing_cmd,
+ "no rtp ip-probing",
+ NO_STR RTP_STR "no automatic rtp bind ip selection\n")
+{
+ g_cfg->net_ports.bind_addr_probe = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_ip_dscp,
+ cfg_mgcp_rtp_ip_dscp_cmd,
+ "rtp ip-dscp <0-255>",
+ RTP_STR
+ "Apply IP_TOS to the audio stream (including Osmux)\n" "The DSCP value\n")
+{
+ int dscp = atoi(argv[0]);
+ g_cfg->endp_dscp = dscp;
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd,
+ "rtp ip-tos <0-255>",
+ RTP_STR
+ "Apply IP_TOS to the audio stream\n" "The DSCP value\n")
+#define FORCE_PTIME_STR "Force a fixed ptime for packets sent"
+ DEFUN(cfg_mgcp_rtp_force_ptime,
+ cfg_mgcp_rtp_force_ptime_cmd,
+ "rtp force-ptime (10|20|40)",
+ RTP_STR FORCE_PTIME_STR
+ "The required ptime (packet duration) in ms\n" "10 ms\n20 ms\n40 ms\n")
+{
+ g_cfg->force_ptime = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_rtp_force_ptime,
+ cfg_mgcp_no_rtp_force_ptime_cmd,
+ "no rtp force-ptime", NO_STR RTP_STR FORCE_PTIME_STR)
+{
+ g_cfg->force_ptime = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_fmtp_extra,
+ cfg_mgcp_sdp_fmtp_extra_cmd,
+ "sdp audio fmtp-extra .NAME",
+ "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n"
+ "Extra Information\n")
+{
+ char *txt = argv_concat(argv, argc, 0);
+ if (!txt)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_fmtp_extra, txt);
+ talloc_free(txt);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_allow_transcoding,
+ cfg_mgcp_allow_transcoding_cmd,
+ "allow-transcoding", "Allow transcoding\n")
+{
+ g_cfg->trunk.no_audio_transcoding = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_allow_transcoding,
+ cfg_mgcp_no_allow_transcoding_cmd,
+ "no allow-transcoding", NO_STR "Allow transcoding\n")
+{
+ g_cfg->trunk.no_audio_transcoding = 1;
+ return CMD_SUCCESS;
+}
+
+#define SDP_STR "SDP File related options\n"
+#define AUDIO_STR "Audio payload options\n"
+DEFUN(cfg_mgcp_sdp_payload_number,
+ cfg_mgcp_sdp_payload_number_cmd,
+ "sdp audio-payload number <0-255>",
+ SDP_STR AUDIO_STR "Number\n" "Payload number\n")
+{
+ unsigned int payload = atoi(argv[0]);
+ g_cfg->trunk.audio_payload = payload;
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_number,
+ cfg_mgcp_sdp_payload_number_cmd_old,
+ "sdp audio payload number <0-255>",
+ SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload number\n")
+
+ DEFUN(cfg_mgcp_sdp_payload_name,
+ cfg_mgcp_sdp_payload_name_cmd,
+ "sdp audio-payload name NAME",
+ SDP_STR AUDIO_STR "Name\n" "Payload name\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_name, cfg_mgcp_sdp_payload_name_cmd_old,
+ "sdp audio payload name NAME",
+ SDP_STR AUDIO_STR AUDIO_STR "Name\n" "Payload name\n")
+
+ DEFUN(cfg_mgcp_sdp_payload_send_ptime,
+ cfg_mgcp_sdp_payload_send_ptime_cmd,
+ "sdp audio-payload send-ptime",
+ SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n")
+{
+ g_cfg->trunk.audio_send_ptime = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_sdp_payload_send_ptime,
+ cfg_mgcp_no_sdp_payload_send_ptime_cmd,
+ "no sdp audio-payload send-ptime",
+ NO_STR SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n")
+{
+ g_cfg->trunk.audio_send_ptime = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_payload_send_name,
+ cfg_mgcp_sdp_payload_send_name_cmd,
+ "sdp audio-payload send-name",
+ SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n")
+{
+ g_cfg->trunk.audio_send_name = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_sdp_payload_send_name,
+ cfg_mgcp_no_sdp_payload_send_name_cmd,
+ "no sdp audio-payload send-name",
+ NO_STR SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n")
+{
+ g_cfg->trunk.audio_send_name = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_loop,
+ cfg_mgcp_loop_cmd,
+ "loop (0|1)",
+ "Loop audio for all endpoints on main trunk\n" "Don't Loop\n" "Loop\n")
+{
+ if (g_cfg->osmux) {
+ vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ g_cfg->trunk.audio_loop = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_force_realloc,
+ cfg_mgcp_force_realloc_cmd,
+ "force-realloc (0|1)",
+ "Force endpoint reallocation when the endpoint is still seized\n"
+ "Don't force reallocation\n" "force reallocation\n")
+{
+ g_cfg->trunk.force_realloc = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_accept_all,
+ cfg_mgcp_rtp_accept_all_cmd,
+ "rtp-accept-all (0|1)",
+ "Accept all RTP packets, even when the originating IP/Port does not match\n"
+ "enable filter\n" "disable filter\n")
+{
+ g_cfg->trunk.rtp_accept_all = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_number_endp,
+ cfg_mgcp_number_endp_cmd,
+ "number endpoints <0-65534>",
+ "Number options\n" "Endpoints available\n" "Number endpoints\n")
+{
+ /* + 1 as we start counting at one */
+ g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_omit_rtcp, cfg_mgcp_omit_rtcp_cmd, "rtcp-omit", RTCP_OMIT_STR)
+{
+ g_cfg->trunk.omit_rtcp = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_omit_rtcp,
+ cfg_mgcp_no_omit_rtcp_cmd, "no rtcp-omit", NO_STR RTCP_OMIT_STR)
+{
+ g_cfg->trunk.omit_rtcp = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_patch_rtp_ssrc,
+ cfg_mgcp_patch_rtp_ssrc_cmd,
+ "rtp-patch ssrc", RTP_PATCH_STR "Force a fixed SSRC\n")
+{
+ g_cfg->trunk.force_constant_ssrc = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_patch_rtp_ssrc,
+ cfg_mgcp_no_patch_rtp_ssrc_cmd,
+ "no rtp-patch ssrc", NO_STR RTP_PATCH_STR "Force a fixed SSRC\n")
+{
+ g_cfg->trunk.force_constant_ssrc = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_patch_rtp_ts,
+ cfg_mgcp_patch_rtp_ts_cmd,
+ "rtp-patch timestamp", RTP_PATCH_STR "Adjust RTP timestamp\n")
+{
+ g_cfg->trunk.force_aligned_timing = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_patch_rtp_ts,
+ cfg_mgcp_no_patch_rtp_ts_cmd,
+ "no rtp-patch timestamp", NO_STR RTP_PATCH_STR "Adjust RTP timestamp\n")
+{
+ g_cfg->trunk.force_aligned_timing = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_patch_rtp,
+ cfg_mgcp_no_patch_rtp_cmd, "no rtp-patch", NO_STR RTP_PATCH_STR)
+{
+ g_cfg->trunk.force_constant_ssrc = 0;
+ g_cfg->trunk.force_aligned_timing = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_keepalive,
+ cfg_mgcp_rtp_keepalive_cmd,
+ "rtp keep-alive <1-120>",
+ RTP_STR RTP_KEEPALIVE_STR "Keep alive interval in secs\n")
+{
+ mgcp_trunk_set_keepalive(&g_cfg->trunk, atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_keepalive_once,
+ cfg_mgcp_rtp_keepalive_once_cmd,
+ "rtp keep-alive once",
+ RTP_STR RTP_KEEPALIVE_STR "Send dummy packet only once after CRCX/MDCX\n")
+{
+ mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_ONCE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_rtp_keepalive,
+ cfg_mgcp_no_rtp_keepalive_cmd,
+ "no rtp keep-alive", NO_STR RTP_STR RTP_KEEPALIVE_STR)
+{
+ mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_NEVER);
+ return CMD_SUCCESS;
+}
+
+#define CALL_AGENT_STR "Callagent information\n"
+DEFUN(cfg_mgcp_agent_addr,
+ cfg_mgcp_agent_addr_cmd,
+ "call-agent ip A.B.C.D",
+ CALL_AGENT_STR IP_STR "IPv4 Address of the callagent\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]);
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_agent_addr, cfg_mgcp_agent_addr_cmd_old,
+ "call agent ip A.B.C.D",
+ CALL_AGENT_STR CALL_AGENT_STR IP_STR
+ "IPv4 Address of the callagent\n")
+
+ DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd,
+ "trunk <1-64>", "Configure a SS7 trunk\n" "Trunk Nr\n")
+{
+ struct mgcp_trunk_config *trunk;
+ int index = atoi(argv[0]);
+
+ trunk = mgcp_trunk_num(g_cfg, index);
+ if (!trunk)
+ trunk = mgcp_trunk_alloc(g_cfg, index);
+
+ if (!trunk) {
+ vty_out(vty, "%%Unable to allocate trunk %u.%s",
+ index, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->node = TRUNK_NODE;
+ vty->index = trunk;
+ return CMD_SUCCESS;
+}
+
+static int config_write_trunk(struct vty *vty)
+{
+ struct mgcp_trunk_config *trunk;
+
+ llist_for_each_entry(trunk, &g_cfg->trunks, entry) {
+ vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE);
+ vty_out(vty, " sdp audio-payload number %d%s",
+ trunk->audio_payload, VTY_NEWLINE);
+ vty_out(vty, " sdp audio-payload name %s%s",
+ trunk->audio_name, VTY_NEWLINE);
+ vty_out(vty, " %ssdp audio-payload send-ptime%s",
+ trunk->audio_send_ptime ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %ssdp audio-payload send-name%s",
+ trunk->audio_send_name ? "" : "no ", VTY_NEWLINE);
+
+ if (trunk->keepalive_interval == MGCP_KEEPALIVE_ONCE)
+ vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE);
+ else if (trunk->keepalive_interval)
+ vty_out(vty, " rtp keep-alive %d%s",
+ trunk->keepalive_interval, VTY_NEWLINE);
+ else
+ vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE);
+ vty_out(vty, " loop %d%s", trunk->audio_loop, VTY_NEWLINE);
+ vty_out(vty, " force-realloc %d%s",
+ trunk->force_realloc, VTY_NEWLINE);
+ vty_out(vty, " rtp-accept-all %d%s",
+ trunk->rtp_accept_all, VTY_NEWLINE);
+ if (trunk->omit_rtcp)
+ vty_out(vty, " rtcp-omit%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE);
+ if (trunk->force_constant_ssrc || trunk->force_aligned_timing) {
+ vty_out(vty, " %srtp-patch ssrc%s",
+ trunk->force_constant_ssrc ? "" : "no ",
+ VTY_NEWLINE);
+ vty_out(vty, " %srtp-patch timestamp%s",
+ trunk->force_aligned_timing ? "" : "no ",
+ VTY_NEWLINE);
+ } else
+ vty_out(vty, " no rtp-patch%s", VTY_NEWLINE);
+ if (trunk->audio_fmtp_extra)
+ vty_out(vty, " sdp audio fmtp-extra %s%s",
+ trunk->audio_fmtp_extra, VTY_NEWLINE);
+ vty_out(vty, " %sallow-transcoding%s",
+ trunk->no_audio_transcoding ? "no " : "", VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_sdp_fmtp_extra,
+ cfg_trunk_sdp_fmtp_extra_cmd,
+ "sdp audio fmtp-extra .NAME",
+ "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n"
+ "Extra Information\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ char *txt = argv_concat(argv, argc, 0);
+ if (!txt)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt);
+ talloc_free(txt);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_payload_number,
+ cfg_trunk_payload_number_cmd,
+ "sdp audio-payload number <0-255>",
+ SDP_STR AUDIO_STR "Number\n" "Payload Number\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ unsigned int payload = atoi(argv[0]);
+
+ trunk->audio_payload = payload;
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_trunk_payload_number, cfg_trunk_payload_number_cmd_old,
+ "sdp audio payload number <0-255>",
+ SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload Number\n")
+
+ DEFUN(cfg_trunk_payload_name,
+ cfg_trunk_payload_name_cmd,
+ "sdp audio-payload name NAME",
+ SDP_STR AUDIO_STR "Payload\n" "Payload Name\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+
+ osmo_talloc_replace_string(g_cfg, &trunk->audio_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_trunk_payload_name, cfg_trunk_payload_name_cmd_old,
+ "sdp audio payload name NAME",
+ SDP_STR AUDIO_STR AUDIO_STR "Payload\n" "Payload Name\n")
+
+ DEFUN(cfg_trunk_loop,
+ cfg_trunk_loop_cmd,
+ "loop (0|1)",
+ "Loop audio for all endpoints on this trunk\n" "Don't Loop\n" "Loop\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+
+ if (g_cfg->osmux) {
+ vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trunk->audio_loop = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_sdp_payload_send_ptime,
+ cfg_trunk_sdp_payload_send_ptime_cmd,
+ "sdp audio-payload send-ptime",
+ SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->audio_send_ptime = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_sdp_payload_send_ptime,
+ cfg_trunk_no_sdp_payload_send_ptime_cmd,
+ "no sdp audio-payload send-ptime",
+ NO_STR SDP_STR AUDIO_STR "Send SDP ptime (packet duration) attribute\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->audio_send_ptime = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_sdp_payload_send_name,
+ cfg_trunk_sdp_payload_send_name_cmd,
+ "sdp audio-payload send-name",
+ SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->audio_send_name = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_sdp_payload_send_name,
+ cfg_trunk_no_sdp_payload_send_name_cmd,
+ "no sdp audio-payload send-name",
+ NO_STR SDP_STR AUDIO_STR "Send SDP rtpmap with the audio name\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->audio_send_name = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_omit_rtcp, cfg_trunk_omit_rtcp_cmd, "rtcp-omit", RTCP_OMIT_STR)
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->omit_rtcp = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_omit_rtcp,
+ cfg_trunk_no_omit_rtcp_cmd, "no rtcp-omit", NO_STR RTCP_OMIT_STR)
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->omit_rtcp = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_patch_rtp_ssrc,
+ cfg_trunk_patch_rtp_ssrc_cmd,
+ "rtp-patch ssrc", RTP_PATCH_STR "Force a fixed SSRC\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->force_constant_ssrc = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_patch_rtp_ssrc,
+ cfg_trunk_no_patch_rtp_ssrc_cmd,
+ "no rtp-patch ssrc", NO_STR RTP_PATCH_STR "Force a fixed SSRC\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->force_constant_ssrc = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_patch_rtp_ts,
+ cfg_trunk_patch_rtp_ts_cmd,
+ "rtp-patch timestamp", RTP_PATCH_STR "Adjust RTP timestamp\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->force_aligned_timing = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_patch_rtp_ts,
+ cfg_trunk_no_patch_rtp_ts_cmd,
+ "no rtp-patch timestamp", NO_STR RTP_PATCH_STR "Adjust RTP timestamp\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->force_aligned_timing = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_patch_rtp,
+ cfg_trunk_no_patch_rtp_cmd, "no rtp-patch", NO_STR RTP_PATCH_STR)
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->force_constant_ssrc = 0;
+ trunk->force_aligned_timing = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_rtp_keepalive,
+ cfg_trunk_rtp_keepalive_cmd,
+ "rtp keep-alive <1-120>",
+ RTP_STR RTP_KEEPALIVE_STR "Keep-alive interval in secs\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ mgcp_trunk_set_keepalive(trunk, atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_rtp_keepalive_once,
+ cfg_trunk_rtp_keepalive_once_cmd,
+ "rtp keep-alive once",
+ RTP_STR RTP_KEEPALIVE_STR "Send dummy packet only once after CRCX/MDCX\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_rtp_keepalive,
+ cfg_trunk_no_rtp_keepalive_cmd,
+ "no rtp keep-alive", NO_STR RTP_STR RTP_KEEPALIVE_STR)
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ mgcp_trunk_set_keepalive(trunk, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_allow_transcoding,
+ cfg_trunk_allow_transcoding_cmd,
+ "allow-transcoding", "Allow transcoding\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->no_audio_transcoding = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_no_allow_transcoding,
+ cfg_trunk_no_allow_transcoding_cmd,
+ "no allow-transcoding", NO_STR "Allow transcoding\n")
+{
+ struct mgcp_trunk_config *trunk = vty->index;
+ trunk->no_audio_transcoding = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(loop_conn,
+ loop_conn_cmd,
+ "loop-endpoint <0-64> NAME (0|1)",
+ "Loop a given endpoint\n" "Trunk number\n"
+ "The name in hex of the endpoint\n" "Disable the loop\n"
+ "Enable the loop\n")
+{
+ struct mgcp_trunk_config *trunk;
+ struct mgcp_endpoint *endp;
+ struct mgcp_conn *conn;
+
+ trunk = find_trunk(g_cfg, atoi(argv[0]));
+ if (!trunk) {
+ vty_out(vty, "%%Trunk %d not found in the config.%s",
+ atoi(argv[0]), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!trunk->endpoints) {
+ vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+ trunk->trunk_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ int endp_no = strtoul(argv[1], NULL, 16);
+ if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+ vty_out(vty, "Loopback number %s/%d is invalid.%s",
+ argv[1], endp_no, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ endp = &trunk->endpoints[endp_no];
+ int loop = atoi(argv[2]);
+ llist_for_each_entry(conn, &endp->conns, entry) {
+ if (conn->type == MGCP_CONN_TYPE_RTP)
+ /* Handle it like a MDCX, switch on SSRC patching if enabled */
+ mgcp_rtp_end_config(endp, 1, &conn->u.rtp.end);
+ else {
+ /* FIXME: Introduce support for other connection (E1)
+ * types when implementation is available */
+ vty_out(vty, "%%Can't enable SSRC patching,"
+ "connection %s is not an RTP connection.%s",
+ mgcp_conn_dump(conn), VTY_NEWLINE);
+ }
+
+ if (loop)
+ conn->mode = MGCP_CONN_LOOPBACK;
+ else
+ conn->mode = conn->mode_orig;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(tap_rtp,
+ tap_rtp_cmd,
+ "tap-rtp <0-64> ENDPOINT CONN (in|out) A.B.C.D <0-65534>",
+ "Forward data on endpoint to a different system\n" "Trunk number\n"
+ "The endpoint in hex\n"
+ "The connection id in hex\n"
+ "Forward incoming data\n"
+ "Forward leaving data\n"
+ "destination IP of the data\n" "destination port\n")
+{
+ struct mgcp_rtp_tap *tap;
+ struct mgcp_trunk_config *trunk;
+ struct mgcp_endpoint *endp;
+ struct mgcp_conn_rtp *conn;
+ uint32_t conn_id;
+
+ trunk = find_trunk(g_cfg, atoi(argv[0]));
+ if (!trunk) {
+ vty_out(vty, "%%Trunk %d not found in the config.%s",
+ atoi(argv[0]), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!trunk->endpoints) {
+ vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+ trunk->trunk_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ int endp_no = strtoul(argv[1], NULL, 16);
+ if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+ vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+ argv[1], endp_no, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ endp = &trunk->endpoints[endp_no];
+
+ conn_id = strtoul(argv[2], NULL, 10);
+ conn = mgcp_conn_get_rtp(endp, conn_id);
+ if (!conn) {
+ vty_out(vty, "Conn ID %s/%d is invalid.%s",
+ argv[2], conn_id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(argv[3], "in") == 0)
+ tap = &conn->tap_in;
+ else if (strcmp(argv[3], "out") == 0)
+ tap = &conn->tap_out;
+ else {
+ vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ memset(&tap->forward, 0, sizeof(tap->forward));
+ inet_aton(argv[4], &tap->forward.sin_addr);
+ tap->forward.sin_port = htons(atoi(argv[5]));
+ tap->enabled = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(free_endp, free_endp_cmd,
+ "free-endpoint <0-64> NUMBER",
+ "Free the given endpoint\n" "Trunk number\n" "Endpoint number in hex.\n")
+{
+ struct mgcp_trunk_config *trunk;
+ struct mgcp_endpoint *endp;
+
+ trunk = find_trunk(g_cfg, atoi(argv[0]));
+ if (!trunk) {
+ vty_out(vty, "%%Trunk %d not found in the config.%s",
+ atoi(argv[0]), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!trunk->endpoints) {
+ vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+ trunk->trunk_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ int endp_no = strtoul(argv[1], NULL, 16);
+ if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+ vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+ argv[1], endp_no, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ endp = &trunk->endpoints[endp_no];
+ mgcp_release_endp(endp);
+ return CMD_SUCCESS;
+}
+
+DEFUN(reset_endp, reset_endp_cmd,
+ "reset-endpoint <0-64> NUMBER",
+ "Reset the given endpoint\n" "Trunk number\n" "Endpoint number in hex.\n")
+{
+ struct mgcp_trunk_config *trunk;
+ struct mgcp_endpoint *endp;
+ int endp_no, rc;
+
+ trunk = find_trunk(g_cfg, atoi(argv[0]));
+ if (!trunk) {
+ vty_out(vty, "%%Trunk %d not found in the config.%s",
+ atoi(argv[0]), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!trunk->endpoints) {
+ vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+ trunk->trunk_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ endp_no = strtoul(argv[1], NULL, 16);
+ if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+ vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+ argv[1], endp_no, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ endp = &trunk->endpoints[endp_no];
+ rc = mgcp_send_reset_ep(endp, ENDPOINT_NUMBER(endp));
+ if (rc < 0) {
+ vty_out(vty, "Error %d sending reset.%s", rc, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(reset_all_endp, reset_all_endp_cmd,
+ "reset-all-endpoints", "Reset all endpoints\n")
+{
+ int rc;
+
+ rc = mgcp_send_reset_all(g_cfg);
+ if (rc < 0) {
+ vty_out(vty, "Error %d during endpoint reset.%s",
+ rc, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+#define OSMUX_STR "RTP multiplexing\n"
+DEFUN(cfg_mgcp_osmux,
+ cfg_mgcp_osmux_cmd,
+ "osmux (on|off|only)",
+ OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n")
+{
+ if (strcmp(argv[0], "off") == 0) {
+ g_cfg->osmux = OSMUX_USAGE_OFF;
+ return CMD_SUCCESS;
+ }
+
+ /* Since OSMUX support is not finished, we do not
+ * allow to turn it on yet. */
+ vty_out(vty, "OSMUX currently unavailable in this software version.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+
+ if (strcmp(argv[0], "on") == 0)
+ g_cfg->osmux = OSMUX_USAGE_ON;
+ else if (strcmp(argv[0], "only") == 0)
+ g_cfg->osmux = OSMUX_USAGE_ONLY;
+
+ if (g_cfg->trunk.audio_loop) {
+ vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_ip,
+ cfg_mgcp_osmux_ip_cmd,
+ "osmux bind-ip A.B.C.D", OSMUX_STR IP_STR "IPv4 Address to bind to\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->osmux_addr, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_batch_factor,
+ cfg_mgcp_osmux_batch_factor_cmd,
+ "osmux batch-factor <1-8>",
+ OSMUX_STR "Batching factor\n" "Number of messages in the batch\n")
+{
+ g_cfg->osmux_batch = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_batch_size,
+ cfg_mgcp_osmux_batch_size_cmd,
+ "osmux batch-size <1-65535>",
+ OSMUX_STR "batch size\n" "Batch size in bytes\n")
+{
+ g_cfg->osmux_batch_size = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_port,
+ cfg_mgcp_osmux_port_cmd,
+ "osmux port <1-65535>", OSMUX_STR "port\n" "UDP port\n")
+{
+ g_cfg->osmux_port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_osmux_dummy,
+ cfg_mgcp_osmux_dummy_cmd,
+ "osmux dummy (on|off)",
+ OSMUX_STR "Dummy padding\n" "Enable dummy padding\n"
+ "Disable dummy padding\n")
+{
+ if (strcmp(argv[0], "on") == 0)
+ g_cfg->osmux_dummy = 1;
+ else if (strcmp(argv[0], "off") == 0)
+ g_cfg->osmux_dummy = 0;
+
+ return CMD_SUCCESS;
+}
+
+int mgcp_vty_init(void)
+{
+ install_element_ve(&show_mgcp_cmd);
+ install_element(ENABLE_NODE, &loop_conn_cmd);
+ install_element(ENABLE_NODE, &tap_rtp_cmd);
+ install_element(ENABLE_NODE, &free_endp_cmd);
+ install_element(ENABLE_NODE, &reset_endp_cmd);
+ install_element(ENABLE_NODE, &reset_all_endp_cmd);
+
+ install_element(CONFIG_NODE, &cfg_mgcp_cmd);
+ install_node(&mgcp_node, config_write_mgcp);
+
+ install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_probing_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_probing_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_force_ptime_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_rtp_force_ptime_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_once_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_rtp_keepalive_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd_old);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd_old);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd_old);
+ install_element(MGCP_NODE, &cfg_mgcp_loop_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_force_realloc_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_accept_all_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_omit_rtcp_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_omit_rtcp_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ssrc_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ssrc_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ts_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ts_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_fmtp_extra_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_ptime_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_ptime_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_name_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_name_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_osmux_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_osmux_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_factor_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_size_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_osmux_port_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_osmux_dummy_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_allow_transcoding_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd);
+
+ install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd);
+ install_node(&trunk_node, config_write_trunk);
+ install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_once_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_rtp_keepalive_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd_old);
+ install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd_old);
+ install_element(TRUNK_NODE, &cfg_trunk_loop_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_omit_rtcp_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_omit_rtcp_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ssrc_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ssrc_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ts_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ts_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_sdp_fmtp_extra_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_ptime_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_ptime_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_name_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_name_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_allow_transcoding_cmd);
+ install_element(TRUNK_NODE, &cfg_trunk_no_allow_transcoding_cmd);
+
+ return 0;
+}
+
+static int allocate_trunk(struct mgcp_trunk_config *trunk)
+{
+ if (mgcp_endpoints_allocate(trunk) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to allocate %d endpoints on trunk %d.\n",
+ trunk->number_endpoints, trunk->trunk_nr);
+ return -1;
+ }
+
+ return 0;
+}
+
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg,
+ enum mgcp_role role)
+{
+ int rc;
+ struct mgcp_trunk_config *trunk;
+
+ cfg->osmux_port = OSMUX_PORT;
+ cfg->osmux_batch = 4;
+ cfg->osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX;
+
+ g_cfg = cfg;
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ config_file);
+ return rc;
+ }
+
+ if (!g_cfg->source_addr) {
+ fprintf(stderr, "You need to specify a bind address.\n");
+ return -1;
+ }
+
+ if (allocate_trunk(&g_cfg->trunk) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to initialize the virtual trunk.\n");
+ return -1;
+ }
+
+ llist_for_each_entry(trunk, &g_cfg->trunks, entry) {
+ if (allocate_trunk(trunk) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to initialize E1 trunk %d.\n",
+ trunk->trunk_nr);
+ return -1;
+ }
+ }
+ cfg->role = role;
+
+ return 0;
+}