create libosmo-mgcp and osmo-mgw by copying legacy code
This a cosmetic commit, copying libosmo-legacy-mgcp to libosmo-mgcp and
osmo-bsc_mgcp to osmo-mgw 1:1 at first, to provide a basis for next patches
that highlight the changes from legacy to new code.
Until osmo-msc and osmo-bsc are adjusted to operate with the new code, we will
keep the legacy code alongside the new code. The legacy code might be dropped
later.
Change-Id: Idf54481754a1765bdb2d0d7033bc0d7dc2018024
diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am
new file mode 100644
index 0000000..bcf6242
--- /dev/null
+++ b/src/libosmo-mgcp/Makefile.am
@@ -0,0 +1,50 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBBCG729_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(LIBBCG729_LIBS) \
+ $(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!
+LEGACY_MGCP_LIBVERSION=0:0:0
+
+lib_LTLIBRARIES = \
+ libosmo-legacy-mgcp.la \
+ $(NULL)
+
+noinst_HEADERS = \
+ g711common.h \
+ $(NULL)
+
+libosmo_legacy_mgcp_la_SOURCES = \
+ mgcp_common.c \
+ mgcp_protocol.c \
+ mgcp_network.c \
+ mgcp_vty.c \
+ mgcp_osmux.c \
+ mgcp_sdp.c \
+ $(NULL)
+if BUILD_MGCP_TRANSCODING
+libosmo_legacy_mgcp_la_SOURCES += \
+ mgcp_transcode.c \
+ $(NULL)
+endif
+
+libosmo_legacy_mgcp_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LEGACY_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_common.c b/src/libosmo-mgcp/mgcp_common.c
new file mode 100644
index 0000000..444a060
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_common.c
@@ -0,0 +1,54 @@
+/* Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* Implementations useful both for the MGCP GW as well as MGCP GW clients */
+
+/*
+ * (C) 2016 by sysmocom s.m.f.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 <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/legacy_mgcp/mgcp.h>
+
+const struct value_string mgcp_connection_mode_strs[] = {
+ { MGCP_CONN_NONE, "none" },
+ { MGCP_CONN_RECV_SEND, "sendrecv" },
+ { MGCP_CONN_SEND_ONLY, "sendonly" },
+ { MGCP_CONN_RECV_ONLY, "recvonly" },
+ { MGCP_CONN_LOOPBACK, "loopback" },
+ { 0, NULL }
+};
+
+/* Ensure that the msg->l2h is NUL terminated. */
+int mgcp_msg_terminate_nul(struct msgb *msg)
+{
+ unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */
+ if (tail[-1] == '\0')
+ /* nothing to do */;
+ else if (msgb_tailroom(msg) > 0)
+ tail[0] = '\0';
+ else if (tail[-1] == '\r' || tail[-1] == '\n')
+ tail[-1] = '\0';
+ else {
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: "
+ "Length: %d, Buffer size: %d\n",
+ msgb_l2len(msg), msg->data_len);
+ return -ENOTSUP;
+ }
+ return 0;
+}
diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c
new file mode 100644
index 0000000..8ccfb42
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_network.c
@@ -0,0 +1,1166 @@
+/* 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/netif/rtp.h>
+
+#include <osmocom/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.h>
+
+#include <osmocom/legacy_mgcp/osmux.h>
+
+#warning "Make use of the rtp proxy code"
+
+
+#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,
+};
+
+/**
+ * This does not need to be a precision timestamp and
+ * is allowed to wrap quite fast. The returned value is
+ * 1/unit seconds.
+ */
+static uint32_t get_current_ts(unsigned unit)
+{
+ struct timespec tp;
+ uint64_t ret;
+
+ if (!unit)
+ return 0;
+
+ memset(&tp, 0, sizeof(tp));
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Getting the clock failed.\n");
+
+ /* convert it to 1/unit seconds */
+ ret = tp.tv_sec;
+ ret *= unit;
+ ret += (int64_t)tp.tv_nsec * unit / 1000 / 1000 / 1000;
+
+ return ret;
+}
+
+int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
+{
+ struct sockaddr_in out;
+ 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));
+}
+
+int mgcp_send_dummy(struct mgcp_endpoint *endp)
+{
+ static char buf[] = { MGCP_DUMMY_LOAD };
+ int rc;
+ int was_rtcp = 0;
+
+ rc = mgcp_udp_send(endp->net_end.rtp.fd, &endp->net_end.addr,
+ endp->net_end.rtp_port, buf, 1);
+
+ if (rc == -1)
+ goto failed;
+
+ if (endp->tcfg->omit_rtcp)
+ return rc;
+
+ was_rtcp = 1;
+ rc = mgcp_udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr,
+ endp->net_end.rtcp_port, buf, 1);
+
+ if (rc >= 0)
+ return rc;
+
+failed:
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to send dummy %s packet: %s on: 0x%x to %s:%d\n",
+ was_rtcp ? "RTCP" : "RTP",
+ strerror(errno), ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr),
+ was_rtcp ? endp->net_end.rtcp_port : endp->net_end.rtp_port);
+
+ return -1;
+}
+
+static int32_t compute_timestamp_aligment_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;
+}
+
+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(DLMGCP, 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 in %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),
+ endp->conn_mode);
+ }
+ return 0;
+ }
+
+ tsdelta =
+ (int32_t)(timestamp - sstate->last_timestamp) /
+ (int16_t)(seq - sstate->last_seq);
+
+ if (tsdelta == 0) {
+ /* Don't update *tsdelta_out */
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "The %s timestamp delta is %d "
+ "on 0x%x SSRC: %u timestamp: %u "
+ "from %s:%d in %d\n",
+ text, tsdelta,
+ ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+
+ return 0;
+ }
+
+ if (sstate->last_tsdelta != tsdelta) {
+ if (sstate->last_tsdelta) {
+ LOGP(DLMGCP, LOGL_INFO,
+ "The %s timestamp delta changes from %d to %d "
+ "on 0x%x SSRC: %u timestamp: %u from %s:%d in %d\n",
+ text, sstate->last_tsdelta, tsdelta,
+ ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ }
+ }
+
+ if (tsdelta_out)
+ *tsdelta_out = tsdelta;
+
+ timestamp_error =
+ compute_timestamp_aligment_error(sstate, state->packet_duration,
+ timestamp);
+
+ if (timestamp_error) {
+ sstate->err_ts_counter += 1;
+ LOGP(DLMGCP, 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 in mode %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),
+ endp->conn_mode, 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(DLMGCP, LOGL_NOTICE,
+ "A fixed packet duration is not available on 0x%x, "
+ "using last output timestamp delta instead: %d "
+ "from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp), tsdelta,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ } else {
+ tsdelta = rtp_end->codec.rate * 20 / 1000;
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Fixed packet duration and last timestamp delta "
+ "are not available on 0x%x, "
+ "using fixed 20ms instead: %d "
+ "from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp), tsdelta,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ }
+ }
+
+ 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(DLMGCP, LOGL_NOTICE,
+ "Timestamp offset change on 0x%x SSRC: %u "
+ "SeqNo delta: %d, TS offset: %d, "
+ "from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ delta_seq, state->timestamp_offset,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ }
+
+ 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 timestamp_error = 0;
+ int ptime = state->packet_duration;
+
+ /* Align according to: T + Toffs - Tlast = k * Tptime */
+
+ timestamp_error = compute_timestamp_aligment_error(
+ &state->out_stream, ptime,
+ timestamp + state->timestamp_offset);
+
+ if (timestamp_error) {
+ state->timestamp_offset += ptime - timestamp_error;
+
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Corrected timestamp alignment error of %d on 0x%x SSRC: %u "
+ "new TS offset: %d, "
+ "from %s:%d in %d\n",
+ timestamp_error,
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ state->timestamp_offset, inet_ntoa(addr->sin_addr),
+ ntohs(addr->sin_port), endp->conn_mode);
+ }
+
+ OSMO_ASSERT(compute_timestamp_aligment_error(&state->out_stream, ptime,
+ timestamp + state->timestamp_offset) == 0);
+
+ return timestamp_error;
+}
+
+int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end,
+ char *data, int *len, int buf_size)
+{
+ return 0;
+}
+
+int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end,
+ struct mgcp_rtp_end *src_end)
+{
+ return 0;
+}
+
+void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
+ int *payload_type,
+ const char**audio_name,
+ const char**fmtp_extra)
+{
+ /* Use the BTS side parameters when passing the SDP data (for
+ * downlink) to the net peer.
+ */
+ *payload_type = endp->bts_end.codec.payload_type;
+ *audio_name = endp->bts_end.codec.audio_name;
+ *fmtp_extra = endp->bts_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(DLMGCP, 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(DLMGCP, LOGL_INFO,
+ "Initializing stream on 0x%x SSRC: %u timestamp: %u "
+ "pkt-duration: %d, from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ state->seq_offset, state->packet_duration,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ if (state->packet_duration == 0) {
+ state->packet_duration = rtp_end->codec.rate * 20 / 1000;
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Fixed packet duration is not available on 0x%x, "
+ "using fixed 20ms instead: %d from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp), state->packet_duration,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ }
+ } else if (state->in_stream.ssrc != ssrc) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "The SSRC changed on 0x%x: %u -> %u "
+ "from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp),
+ state->in_stream.ssrc, rtp_hdr->ssrc,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+
+ 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(DLMGCP, LOGL_NOTICE,
+ "SSRC patching enabled on 0x%x SSRC: %u "
+ "SeqNo offset: %d, TS offset: %d "
+ "from %s:%d in %d\n",
+ ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
+ state->seq_offset, state->timestamp_offset,
+ inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+ endp->conn_mode);
+ }
+
+ 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(DLMGCP, "Payload hdr payload %u -> endp payload %u\n",
+ rtp_hdr->payload_type, payload);
+ rtp_hdr->payload_type = payload;
+#endif
+}
+
+/*
+ * The below code is for dispatching. We have a dedicated port for
+ * the data coming from the net and one to discover the BTS.
+ */
+static int forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, int len)
+{
+ if (!tap->enabled)
+ return 0;
+
+ return sendto(fd, buf, len, 0,
+ (struct sockaddr *)&tap->forward, sizeof(tap->forward));
+}
+
+static int mgcp_send_transcoder(struct mgcp_rtp_end *end,
+ struct mgcp_config *cfg, int is_rtp,
+ const char *buf, int len)
+{
+ int rc;
+ int port;
+ struct sockaddr_in addr;
+
+ port = is_rtp ? end->rtp_port : end->rtcp_port;
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr = cfg->transcoder_in;
+ addr.sin_port = port;
+
+ rc = sendto(is_rtp ?
+ end->rtp.fd :
+ end->rtcp.fd, buf, len, 0,
+ (struct sockaddr *) &addr, sizeof(addr));
+
+ if (rc != len)
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to send data to the transcoder: %s\n",
+ strerror(errno));
+
+ return rc;
+}
+
+int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp,
+ struct sockaddr_in *addr, char *buf, int rc)
+{
+ struct mgcp_trunk_config *tcfg = endp->tcfg;
+ struct mgcp_rtp_end *rtp_end;
+ struct mgcp_rtp_state *rtp_state;
+ int tap_idx;
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n",
+ ENDPOINT_NUMBER(endp),
+ dest == MGCP_DEST_NET? "net" : "bts",
+ tcfg->audio_loop,
+ endp->conn_mode,
+ endp->conn_mode == MGCP_CONN_LOOPBACK);
+
+ /* For loop toggle the destination and then dispatch. */
+ if (tcfg->audio_loop)
+ dest = !dest;
+
+ /* Loop based on the conn_mode, maybe undoing the above */
+ if (endp->conn_mode == MGCP_CONN_LOOPBACK)
+ dest = !dest;
+
+ if (dest == MGCP_DEST_NET) {
+ rtp_end = &endp->net_end;
+ rtp_state = &endp->bts_state;
+ tap_idx = MGCP_TAP_NET_OUT;
+ } else {
+ rtp_end = &endp->bts_end;
+ rtp_state = &endp->net_state;
+ tap_idx = MGCP_TAP_BTS_OUT;
+ }
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n",
+ ENDPOINT_NUMBER(endp),
+ dest == MGCP_DEST_NET? "net" : "bts",
+
+ inet_ntoa(endp->net_end.addr),
+ ntohs(endp->net_end.rtp_port),
+ ntohs(endp->net_end.rtcp_port),
+
+ inet_ntoa(endp->bts_end.addr),
+ ntohs(endp->bts_end.rtp_port),
+ ntohs(endp->bts_end.rtcp_port),
+
+ inet_ntoa(rtp_end->addr),
+ ntohs(rtp_end->rtp_port),
+ ntohs(rtp_end->rtcp_port)
+ );
+
+ if (!rtp_end->output_enabled) {
+ rtp_end->dropped_packets += 1;
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint %x output disabled, drop to %s %s %d %d\n",
+ ENDPOINT_NUMBER(endp),
+ dest == MGCP_DEST_NET? "net" : "bts",
+ 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 len = rc;
+ do {
+ cont = endp->cfg->rtp_processing_cb(endp, rtp_end,
+ buf, &len, RTP_BUF_SIZE);
+ if (cont < 0)
+ break;
+
+ mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len);
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint %x process/send to %s %s %d %d\n",
+ ENDPOINT_NUMBER(endp),
+ (dest == MGCP_DEST_NET)? "net" : "bts",
+ inet_ntoa(rtp_end->addr),
+ ntohs(rtp_end->rtp_port),
+ ntohs(rtp_end->rtcp_port)
+ );
+ forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx],
+ buf, len);
+
+ /* 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 (tap_idx == MGCP_TAP_BTS_OUT
+ && !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;
+ }
+
+ rc = mgcp_udp_send(rtp_end->rtp.fd,
+ &rtp_end->addr,
+ rtp_end->rtp_port, buf, len);
+
+ if (rc <= 0)
+ return rc;
+ nbytes += rc;
+ len = cont;
+ } while (len > 0);
+ return nbytes;
+ } else if (!tcfg->omit_rtcp) {
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint %x send to %s %s %d %d\n",
+ ENDPOINT_NUMBER(endp),
+ dest == MGCP_DEST_NET? "net" : "bts",
+ inet_ntoa(rtp_end->addr),
+ ntohs(rtp_end->rtp_port),
+ ntohs(rtp_end->rtcp_port)
+ );
+
+ return mgcp_udp_send(rtp_end->rtcp.fd,
+ &rtp_end->addr,
+ rtp_end->rtcp_port, buf, rc);
+ }
+
+ return 0;
+}
+
+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);
+
+ rc = recvfrom(fd, buf, bufsize, 0,
+ (struct sockaddr *) addr, &slen);
+ if (rc < 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n",
+ ENDPOINT_NUMBER(endp), errno, strerror(errno));
+ return -1;
+ }
+
+ /* do not forward aynthing... maybe there is a packet from the bts */
+ if (!endp->allocated)
+ return -1;
+
+ #warning "Slight spec violation. With connection mode recvonly we should attempt to forward."
+
+ return rc;
+}
+
+static int rtp_data_net(struct osmo_fd *fd, unsigned int what)
+{
+ char buf[RTP_BUF_SIZE];
+ struct sockaddr_in addr;
+ struct mgcp_endpoint *endp;
+ int rc, proto;
+
+ endp = (struct mgcp_endpoint *) fd->data;
+
+ rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf));
+ if (rc <= 0)
+ return -1;
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "endpoint %x",
+ ENDPOINT_NUMBER(endp));
+ LOGPC(DLMGCP, LOGL_DEBUG,
+ " from net %s %d",
+ inet_ntoa(addr.sin_addr),
+ ntohs(addr.sin_port));
+ LOGPC(DLMGCP, LOGL_DEBUG,
+ " net_end %s %d %d",
+ inet_ntoa(endp->net_end.addr),
+ ntohs(endp->net_end.rtp_port),
+ ntohs(endp->net_end.rtcp_port));
+ LOGPC(DLMGCP, LOGL_DEBUG,
+ " bts_end %s %d %d\n",
+ inet_ntoa(endp->bts_end.addr),
+ ntohs(endp->bts_end.rtp_port),
+ ntohs(endp->bts_end.rtcp_port)
+ );
+
+ if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "rtp_data_net: Endpoint 0x%x data from wrong address %s vs. ",
+ ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr));
+ LOGPC(DLMGCP, LOGL_ERROR,
+ "%s\n", inet_ntoa(endp->net_end.addr));
+ return -1;
+ }
+
+ switch(endp->type) {
+ case MGCP_RTP_DEFAULT:
+ case MGCP_RTP_TRANSCODED:
+ if (endp->net_end.rtp_port != addr.sin_port &&
+ endp->net_end.rtcp_port != addr.sin_port) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "rtp_data_net: Data from wrong source port %d on 0x%x\n",
+ ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+ break;
+ case MGCP_OSMUX_BSC:
+ case MGCP_OSMUX_BSC_NAT:
+ break;
+ }
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "rtp_data_net: Endpoint %x data from %s %d\n",
+ ENDPOINT_NUMBER(endp),
+ inet_ntoa(addr.sin_addr),
+ ntohs(addr.sin_port));
+
+ /* throw away the dummy message */
+ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+ LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ proto = fd == &endp->net_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+ endp->net_end.packets += 1;
+ endp->net_end.octets += rc;
+
+ forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc);
+
+ switch (endp->type) {
+ case MGCP_RTP_DEFAULT:
+ return mgcp_send(endp, MGCP_DEST_BTS, proto == MGCP_PROTO_RTP,
+ &addr, buf, rc);
+ case MGCP_RTP_TRANSCODED:
+ return mgcp_send_transcoder(&endp->trans_net, endp->cfg,
+ proto == MGCP_PROTO_RTP, buf, rc);
+ case MGCP_OSMUX_BSC_NAT:
+ return osmux_xfrm_to_osmux(MGCP_DEST_BTS, buf, rc, endp);
+ case MGCP_OSMUX_BSC: /* Should not happen */
+ break;
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n",
+ endp->type, ENDPOINT_NUMBER(endp));
+ return 0;
+}
+
+static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr)
+{
+ struct mgcp_config *cfg = endp->cfg;
+
+ if (proto == MGCP_PROTO_RTP && endp->bts_end.rtp_port == 0) {
+ if (!cfg->bts_ip ||
+ memcmp(&addr->sin_addr,
+ &cfg->bts_in, sizeof(cfg->bts_in)) == 0 ||
+ memcmp(&addr->sin_addr,
+ &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) {
+
+ endp->bts_end.rtp_port = addr->sin_port;
+ endp->bts_end.addr = addr->sin_addr;
+
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Found BTS for endpoint: 0x%x on port: %d/%d of %s\n",
+ ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port),
+ ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr));
+ }
+ } else if (proto == MGCP_PROTO_RTCP && endp->bts_end.rtcp_port == 0) {
+ if (memcmp(&endp->bts_end.addr, &addr->sin_addr,
+ sizeof(endp->bts_end.addr)) == 0) {
+ endp->bts_end.rtcp_port = addr->sin_port;
+ }
+ }
+}
+
+static int rtp_data_bts(struct osmo_fd *fd, unsigned int what)
+{
+ char buf[RTP_BUF_SIZE];
+ struct sockaddr_in addr;
+ struct mgcp_endpoint *endp;
+ int rc, proto;
+
+ endp = (struct mgcp_endpoint *) fd->data;
+
+ rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf));
+ if (rc <= 0)
+ return -1;
+
+ proto = fd == &endp->bts_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+
+ /* We have no idea who called us, maybe it is the BTS. */
+ /* it was the BTS... */
+ discover_bts(endp, proto, &addr);
+
+ if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "rtp_data_bts: Data from wrong bts %s on 0x%x\n",
+ inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ if (endp->bts_end.rtp_port != addr.sin_port &&
+ endp->bts_end.rtcp_port != addr.sin_port) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "rtp_data_bts: ata from wrong bts source port %d on 0x%x\n",
+ ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "rtp_data_bts: Endpoint %x data from %s %d\n",
+ ENDPOINT_NUMBER(endp),
+ inet_ntoa(addr.sin_addr),
+ ntohs(addr.sin_port));
+
+ /* throw away the dummy message */
+ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+ LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ /* do this before the loop handling */
+ endp->bts_end.packets += 1;
+ endp->bts_end.octets += rc;
+
+ forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc);
+
+ switch (endp->type) {
+ case MGCP_RTP_DEFAULT:
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "rtp_data_bts: Endpoint %x MGCP_RTP_DEFAULT\n",
+ ENDPOINT_NUMBER(endp));
+ return mgcp_send(endp, MGCP_DEST_NET, proto == MGCP_PROTO_RTP,
+ &addr, buf, rc);
+ case MGCP_RTP_TRANSCODED:
+ return mgcp_send_transcoder(&endp->trans_bts, endp->cfg,
+ proto == MGCP_PROTO_RTP, buf, rc);
+ case MGCP_OSMUX_BSC:
+ /* OSMUX translation: BTS -> BSC */
+ return osmux_xfrm_to_osmux(MGCP_DEST_NET, buf, rc, endp);
+ case MGCP_OSMUX_BSC_NAT:
+ break; /* Should not happen */
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n",
+ endp->type, ENDPOINT_NUMBER(endp));
+ return 0;
+}
+
+static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp,
+ int dest, struct osmo_fd *fd)
+{
+ char buf[RTP_BUF_SIZE];
+ struct sockaddr_in addr;
+ struct mgcp_config *cfg;
+ int rc, proto;
+
+ cfg = _endp->cfg;
+ rc = receive_from(_endp, fd->fd, &addr, buf, sizeof(buf));
+ if (rc <= 0)
+ return -1;
+
+ proto = fd == &end->rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
+
+ if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Data not coming from transcoder dest: %d %s on 0x%x\n",
+ dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp));
+ return -1;
+ }
+
+ if (end->rtp_port != addr.sin_port &&
+ end->rtcp_port != addr.sin_port) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Data from wrong transcoder dest %d source port %d on 0x%x\n",
+ dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp));
+ return -1;
+ }
+
+ /* throw away the dummy message */
+ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
+ LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n",
+ dest, ENDPOINT_NUMBER(_endp));
+ return 0;
+ }
+
+ end->packets += 1;
+ return mgcp_send(_endp, dest, proto == MGCP_PROTO_RTP, &addr, buf, rc);
+}
+
+static int rtp_data_trans_net(struct osmo_fd *fd, unsigned int what)
+{
+ struct mgcp_endpoint *endp;
+ endp = (struct mgcp_endpoint *) fd->data;
+
+ return rtp_data_transcoder(&endp->trans_net, endp, MGCP_DEST_NET, fd);
+}
+
+static int rtp_data_trans_bts(struct osmo_fd *fd, unsigned int what)
+{
+ struct mgcp_endpoint *endp;
+ endp = (struct mgcp_endpoint *) fd->data;
+
+ return rtp_data_transcoder(&endp->trans_bts, endp, MGCP_DEST_BTS, fd);
+}
+
+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(DLMGCP, LOGL_ERROR, "Failed to create UDP port.\n");
+ return -1;
+ }
+
+ setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ 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;
+ return -1;
+ }
+
+ return 0;
+}
+
+int mgcp_set_ip_tos(int fd, int tos)
+{
+ int ret;
+ ret = setsockopt(fd, IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+ return ret != 0;
+}
+
+static int bind_rtp(struct mgcp_config *cfg, const char *source_addr,
+ struct mgcp_rtp_end *rtp_end, int endpno)
+{
+ if (mgcp_create_bind(source_addr, &rtp_end->rtp,
+ rtp_end->local_port) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n",
+ source_addr, rtp_end->local_port, endpno);
+ goto cleanup0;
+ }
+
+ if (mgcp_create_bind(source_addr, &rtp_end->rtcp,
+ rtp_end->local_port + 1) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n",
+ source_addr, rtp_end->local_port + 1, endpno);
+ goto cleanup1;
+ }
+
+ 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(DLMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n",
+ rtp_end->local_port, endpno);
+ goto cleanup2;
+ }
+
+ rtp_end->rtcp.when = BSC_FD_READ;
+ if (osmo_fd_register(&rtp_end->rtcp) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n",
+ rtp_end->local_port + 1, endpno);
+ 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;
+}
+
+static int int_bind(const char *port,
+ struct mgcp_rtp_end *end, int (*cb)(struct osmo_fd *, unsigned),
+ struct mgcp_endpoint *_endp,
+ const char *source_addr, int rtp_port)
+{
+ if (end->rtp.fd != -1 || end->rtcp.fd != -1) {
+ LOGP(DLMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n",
+ port, ENDPOINT_NUMBER(_endp));
+ mgcp_free_rtp_port(end);
+ }
+
+ end->local_port = rtp_port;
+ end->rtp.cb = cb;
+ end->rtp.data = _endp;
+ end->rtcp.data = _endp;
+ end->rtcp.cb = cb;
+ return bind_rtp(_endp->cfg, source_addr, end, ENDPOINT_NUMBER(_endp));
+}
+
+int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+ return int_bind("bts-port", &endp->bts_end,
+ rtp_data_bts, endp,
+ mgcp_bts_src_addr(endp), rtp_port);
+}
+
+int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+ return int_bind("net-port", &endp->net_end,
+ rtp_data_net, endp,
+ mgcp_net_src_addr(endp), rtp_port);
+}
+
+int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+ return int_bind("trans-net", &endp->trans_net,
+ rtp_data_trans_net, endp,
+ endp->cfg->source_addr, rtp_port);
+}
+
+int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+ return int_bind("trans-bts", &endp->trans_bts,
+ rtp_data_trans_bts, endp,
+ endp->cfg->source_addr, rtp_port);
+}
+
+int 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);
+ }
+
+ return 0;
+}
+
+
+void mgcp_state_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;
+ if (*expected < end->packets) {
+ if (*loss > 0)
+ *loss = INT_MIN;
+ } else {
+ if (*loss < 0)
+ *loss = INT_MAX;
+ }
+}
+
+uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *state)
+{
+ if (!state->stats_initialized)
+ return 0;
+ return state->stats_jitter >> 4;
+}
diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c
new file mode 100644
index 0000000..743d3f9
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_osmux.c
@@ -0,0 +1,583 @@
+/*
+ * (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/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.h>
+#include <osmocom/legacy_mgcp/osmux.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;
+
+static void osmux_deliver(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);
+}
+
+static struct osmux_handle *
+osmux_handle_find_get(struct in_addr *addr, int rem_port)
+{
+ struct osmux_handle *h;
+
+ /* Lookup for existing OSMUX handle for this destination address. */
+ 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;
+}
+
+static void osmux_handle_put(struct osmux_in_handle *in)
+{
+ struct osmux_handle *h;
+
+ /* Lookup for existing OSMUX handle for this destination address. */
+ 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);
+}
+
+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;
+ }
+
+ h->in->osmux_seq = 0; /* sequence number to start OSmux message from */
+ 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;
+ 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;
+}
+
+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;
+}
+
+int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp)
+{
+ int ret;
+ struct msgb *msg;
+
+ msg = msgb_alloc(4096, "RTP");
+ if (!msg)
+ return 0;
+
+ memcpy(msg->data, buf, rc);
+ msgb_put(msg, rc);
+
+ while ((ret = osmux_xfrm_input(endp->osmux.in, msg, endp->osmux.cid)) > 0) {
+ /* batch full, build and deliver it */
+ osmux_xfrm_input_deliver(endp->osmux.in);
+ }
+ return 0;
+}
+
+static struct mgcp_endpoint *
+endpoint_lookup(struct mgcp_config *cfg, int cid,
+ struct in_addr *from_addr, int type)
+{
+ struct mgcp_endpoint *tmp = NULL;
+ int i;
+
+ /* Lookup for the endpoint that corresponds to this port */
+ for (i=0; i<cfg->trunk.number_endpoints; i++) {
+ struct in_addr *this;
+
+ tmp = &cfg->trunk.endpoints[i];
+
+ if (!tmp->allocated)
+ continue;
+
+ switch(type) {
+ case MGCP_DEST_NET:
+ this = &tmp->net_end.addr;
+ break;
+ case MGCP_DEST_BTS:
+ this = &tmp->bts_end.addr;
+ break;
+ default:
+ /* Should not ever happen */
+ LOGP(DLMGCP, LOGL_ERROR, "Bad type %d. Fix your code.\n", type);
+ return NULL;
+ }
+
+ if (tmp->osmux.cid == cid && this->s_addr == from_addr->s_addr)
+ return tmp;
+ }
+
+ 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 sockaddr_in addr = {
+ .sin_addr = endp->net_end.addr,
+ .sin_port = endp->net_end.rtp_port,
+ };
+
+ endp->bts_end.octets += msg->len;
+ endp->bts_end.packets++;
+
+ mgcp_send(endp, MGCP_DEST_NET, 1, &addr, (char *)msg->data, msg->len);
+ msgb_free(msg);
+}
+
+static void scheduled_tx_bts_cb(struct msgb *msg, void *data)
+{
+ struct mgcp_endpoint *endp = data;
+ struct sockaddr_in addr = {
+ .sin_addr = endp->bts_end.addr,
+ .sin_port = endp->bts_end.rtp_port,
+ };
+
+ endp->net_end.octets += msg->len;
+ endp->net_end.packets++;
+
+ mgcp_send(endp, MGCP_DEST_BTS, 1, &addr, (char *)msg->data, msg->len);
+ 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;
+
+ 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);
+ if (!endp) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot find an endpoint for circuit_id=%d\n",
+ osmuxh->circuit_id);
+ goto out;
+ }
+ endp->osmux.stats.octets += osmux_chunk_length(msg, rem);
+ endp->osmux.stats.chunks++;
+ rem = msg->len;
+
+ osmux_xfrm_output(osmuxh, &endp->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;
+
+ 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;
+ }
+
+ if (endp->osmux.state == OSMUX_STATE_ENABLED)
+ goto out;
+
+ if (osmux_enable_endpoint(endp, &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;
+
+ 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);
+ if (!endp) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot find an endpoint for circuit_id=%d\n",
+ osmuxh->circuit_id);
+ goto out;
+ }
+ endp->osmux.stats.octets += osmux_chunk_length(msg, rem);
+ endp->osmux.stats.chunks++;
+ rem = msg->len;
+
+ osmux_xfrm_output(osmuxh, &endp->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;
+}
+
+int osmux_enable_endpoint(struct mgcp_endpoint *endp, 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 (endp->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;
+
+ if (endp->osmux.state == OSMUX_STATE_DISABLED) {
+ LOGP(DLMGCP, LOGL_ERROR, "Endpoint %u didn't request Osmux\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
+ osmux_xfrm_output_init(&endp->osmux.out,
+ (endp->osmux.cid * rtp_ssrc_winlen) +
+ (random() % rtp_ssrc_winlen));
+
+ endp->osmux.in = osmux_handle_lookup(endp->cfg, addr, port);
+ if (!endp->osmux.in) {
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot allocate input osmux handle\n");
+ return -1;
+ }
+ if (!osmux_xfrm_input_open_circuit(endp->osmux.in, endp->osmux.cid,
+ endp->cfg->osmux_dummy)) {
+ LOGP(DLMGCP, LOGL_ERROR, "Cannot open osmux circuit %u\n",
+ endp->osmux.cid);
+ return -1;
+ }
+
+ switch (endp->cfg->role) {
+ case MGCP_BSC_NAT:
+ endp->type = MGCP_OSMUX_BSC_NAT;
+ break;
+ case MGCP_BSC:
+ endp->type = MGCP_OSMUX_BSC;
+ break;
+ }
+ endp->osmux.state = OSMUX_STATE_ENABLED;
+
+ return 0;
+}
+
+void osmux_disable_endpoint(struct mgcp_endpoint *endp)
+{
+ LOGP(DLMGCP, LOGL_INFO, "Releasing endpoint %u using Osmux CID %u\n",
+ ENDPOINT_NUMBER(endp), endp->osmux.cid);
+ osmux_xfrm_input_close_circuit(endp->osmux.in, endp->osmux.cid);
+ endp->osmux.state = OSMUX_STATE_DISABLED;
+ endp->osmux.cid = -1;
+ osmux_handle_put(endp->osmux.in);
+}
+
+void osmux_release_cid(struct mgcp_endpoint *endp)
+{
+ if (endp->osmux.allocated_cid >= 0)
+ osmux_put_cid(endp->osmux.allocated_cid);
+ endp->osmux.allocated_cid = -1;
+}
+
+void osmux_allocate_cid(struct mgcp_endpoint *endp)
+{
+ osmux_release_cid(endp);
+ endp->osmux.allocated_cid = osmux_get_cid();
+}
+
+/* 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.
+ */
+int osmux_send_dummy(struct mgcp_endpoint *endp)
+{
+ char buf[1 + sizeof(uint8_t)];
+ struct in_addr addr_unset = {};
+
+ buf[0] = MGCP_DUMMY_LOAD;
+ memcpy(&buf[1], &endp->osmux.cid, sizeof(endp->osmux.cid));
+
+ /* Wait until we have the connection information from MDCX */
+ if (memcmp(&endp->net_end.addr, &addr_unset, sizeof(addr_unset)) == 0)
+ return 0;
+
+ if (endp->osmux.state == OSMUX_STATE_ACTIVATING) {
+ if (osmux_enable_endpoint(endp, &endp->net_end.addr,
+ htons(endp->cfg->osmux_port)) < 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Could not activate osmux in endpoint %d\n",
+ ENDPOINT_NUMBER(endp));
+ }
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Osmux CID %u for %s:%u is now enabled\n",
+ endp->osmux.cid, inet_ntoa(endp->net_end.addr),
+ endp->cfg->osmux_port);
+ }
+ LOGP(DLMGCP, LOGL_DEBUG,
+ "sending OSMUX dummy load to %s CID %u\n",
+ inet_ntoa(endp->net_end.addr), endp->osmux.cid);
+
+ return mgcp_udp_send(osmux_fd.fd, &endp->net_end.addr,
+ htons(endp->cfg->osmux_port), buf, sizeof(buf));
+}
+
+/* bsc-nat allocates/releases the Osmux circuit ID */
+static uint8_t osmux_cid_bitmap[(OSMUX_CID_MAX + 1) / 8];
+
+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;
+}
+
+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;
+}
+
+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..bc1ec0d
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_protocol.c
@@ -0,0 +1,1640 @@
+/* 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/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.h>
+
+#define for_each_non_empty_line(line, save) \
+ for (line = strtok_r(NULL, "\r\n", &save); line;\
+ line = strtok_r(NULL, "\r\n", &save))
+
+
+static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end);
+
+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);
+
+static void create_transcoder(struct mgcp_endpoint *endp);
+static void delete_transcoder(struct mgcp_endpoint *endp);
+
+static int setup_rtp_processing(struct mgcp_endpoint *endp);
+
+static int mgcp_analyze_header(struct mgcp_parse_data *parse, char *data);
+
+/* Display an mgcp message on the log output */
+void display_mgcp_message(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;
+ }
+}
+
+static 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;
+ }
+
+ return 1;
+}
+
+static uint32_t generate_call_id(struct mgcp_config *cfg)
+{
+ int i;
+
+ /* use the call id */
+ ++cfg->last_call_id;
+
+ /* handle wrap around */
+ if (cfg->last_call_id == CI_UNUSED)
+ ++cfg->last_call_id;
+
+ /* callstack can only be of size number_of_endpoints */
+ /* verify that the call id is free, e.g. in case of overrun */
+ for (i = 1; i < cfg->trunk.number_endpoints; ++i)
+ if (cfg->trunk.endpoints[i].ci == cfg->last_call_id)
+ return generate_call_id(cfg);
+
+ return cfg->last_call_id;
+}
+
+/*
+ * 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")
+};
+
+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;
+}
+
+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));
+ display_mgcp_message(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);
+ display_mgcp_message(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);
+}
+
+static int write_response_sdp(struct mgcp_endpoint *endp,
+ char *sdp_record, size_t size, const char *addr)
+{
+ const char *fmtp_extra;
+ const char *audio_name;
+ int payload_type;
+ int len;
+ int nchars;
+
+ endp->cfg->get_net_downlink_format_cb(endp, &payload_type,
+ &audio_name, &fmtp_extra);
+
+ len = snprintf(sdp_record, size,
+ "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",
+ endp->ci, addr, addr);
+
+ if (len < 0 || len >= size)
+ goto buffer_too_small;
+
+ if (payload_type >= 0) {
+ nchars = snprintf(sdp_record + len, size - len,
+ "m=audio %d RTP/AVP %d\r\n",
+ endp->net_end.local_port, payload_type);
+ if (nchars < 0 || nchars >= size - len)
+ goto buffer_too_small;
+
+ len += nchars;
+
+ if (audio_name && endp->tcfg->audio_send_name) {
+ nchars = snprintf(sdp_record + len, size - len,
+ "a=rtpmap:%d %s\r\n",
+ payload_type, audio_name);
+
+ if (nchars < 0 || nchars >= size - len)
+ goto buffer_too_small;
+
+ len += nchars;
+ }
+
+ if (fmtp_extra) {
+ nchars = snprintf(sdp_record + len, size - len,
+ "%s\r\n", fmtp_extra);
+
+ if (nchars < 0 || nchars >= size - len)
+ goto buffer_too_small;
+
+ len += nchars;
+ }
+ }
+ if (endp->bts_end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) {
+ nchars = snprintf(sdp_record + len, size - len,
+ "a=ptime:%d\r\n",
+ endp->bts_end.packet_duration_ms);
+ if (nchars < 0 || nchars >= size - len)
+ goto buffer_too_small;
+
+ len += nchars;
+ }
+
+ return len;
+
+buffer_too_small:
+ LOGP(DLMGCP, LOGL_ERROR, "SDP buffer too small: %zu (needed %d)\n",
+ size, len);
+ return -1;
+}
+
+static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
+ const char *msg, const char *trans_id)
+{
+ const char *addr = endp->cfg->local_ip;
+ char sdp_record[4096];
+ int len;
+ int nchars;
+ char osmux_extension[strlen("\nX-Osmux: 255") + 1];
+
+ if (!addr)
+ addr = mgcp_net_src_addr(endp);
+
+ if (endp->osmux.state == OSMUX_STATE_NEGOTIATING) {
+ sprintf(osmux_extension, "\nX-Osmux: %u", endp->osmux.cid);
+ endp->osmux.state = OSMUX_STATE_ACTIVATING;
+ } else {
+ osmux_extension[0] = '\0';
+ }
+
+ len = snprintf(sdp_record, sizeof(sdp_record),
+ "I: %u%s\n\n", endp->ci, osmux_extension);
+ if (len < 0)
+ return NULL;
+
+ nchars = write_response_sdp(endp, sdp_record + len,
+ sizeof(sdp_record) - len - 1, addr);
+ if (nchars < 0)
+ return NULL;
+
+ len += nchars;
+
+ sdp_record[sizeof(sdp_record) - 1] = '\0';
+
+ return create_resp(endp, 200, " OK", msg, trans_id, NULL, sdp_record);
+}
+
+static void send_dummy(struct mgcp_endpoint *endp)
+{
+ if (endp->osmux.state != OSMUX_STATE_DISABLED)
+ osmux_send_dummy(endp);
+ else
+ mgcp_send_dummy(endp);
+}
+
+/*
+ * 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;
+
+ display_mgcp_message(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 = strline_r((char *) msg->l3h, &pdata.save);
+ pdata.found = mgcp_analyze_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;
+}
+
+/**
+ * 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.
+ */
+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];
+}
+
+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;
+}
+
+/**
+ * @returns 0 when the status line was complete and transaction_id and
+ * endp out parameters are set.
+ */
+static int mgcp_analyze_header(struct mgcp_parse_data *pdata, char *data)
+{
+ int i = 0;
+ char *elem, *save = NULL;
+
+ 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;
+}
+
+static int verify_call_id(const struct mgcp_endpoint *endp,
+ const char *callid)
+{
+ if (strcmp(endp->callid, callid) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n",
+ ENDPOINT_NUMBER(endp), endp->callid, callid);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int verify_ci(const struct mgcp_endpoint *endp,
+ const char *_ci)
+{
+ uint32_t ci = strtoul(_ci, NULL, 10);
+
+ if (ci != endp->ci) {
+ LOGP(DLMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n",
+ ENDPOINT_NUMBER(endp), endp->ci, _ci);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p)
+{
+ if (p->found != 0)
+ return create_err_response(NULL, 500, "AUEP", p->trans);
+ else
+ return create_ok_response(p->endp, 200, "AUEP", p->trans);
+}
+
+static int parse_conn_mode(const char *msg, struct mgcp_endpoint *endp)
+{
+ int ret = 0;
+ if (strcmp(msg, "recvonly") == 0)
+ endp->conn_mode = MGCP_CONN_RECV_ONLY;
+ else if (strcmp(msg, "sendrecv") == 0)
+ endp->conn_mode = MGCP_CONN_RECV_SEND;
+ else if (strcmp(msg, "sendonly") == 0)
+ endp->conn_mode = MGCP_CONN_SEND_ONLY;
+ else if (strcmp(msg, "loopback") == 0)
+ endp->conn_mode = MGCP_CONN_LOOPBACK;
+ else {
+ LOGP(DLMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg);
+ ret = -1;
+ }
+
+ endp->net_end.output_enabled =
+ endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0;
+ endp->bts_end.output_enabled =
+ endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0;
+
+ LOGP(DLMGCP, LOGL_DEBUG, "endpoint %x connection mode '%s' %d output_enabled net %d bts %d\n",
+ ENDPOINT_NUMBER(endp),
+ msg, endp->conn_mode, endp->net_end.output_enabled,
+ endp->bts_end.output_enabled);
+
+ return ret;
+}
+
+static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end,
+ struct mgcp_port_range *range,
+ int (*alloc)(struct mgcp_endpoint *endp, int port))
+{
+ int i;
+
+ if (range->mode == PORT_ALLOC_STATIC) {
+ end->local_alloc = PORT_ALLOC_STATIC;
+ return 0;
+ }
+
+ /* 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 = alloc(endp, range->last_port);
+
+ range->last_port += 2;
+ if (rc == 0) {
+ end->local_alloc = PORT_ALLOC_DYNAMIC;
+ return 0;
+ }
+
+ }
+
+ LOGP(DLMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+}
+
+static int allocate_ports(struct mgcp_endpoint *endp)
+{
+ if (allocate_port(endp, &endp->net_end, &endp->cfg->net_ports,
+ mgcp_bind_net_rtp_port) != 0)
+ return -1;
+
+ if (allocate_port(endp, &endp->bts_end, &endp->cfg->bts_ports,
+ mgcp_bind_bts_rtp_port) != 0) {
+ mgcp_rtp_end_reset(&endp->net_end);
+ return -1;
+ }
+
+ if (endp->cfg->transcoder_ip && endp->tcfg->trunk_type == MGCP_TRUNK_VIRTUAL) {
+ if (allocate_port(endp, &endp->trans_net,
+ &endp->cfg->transcoder_ports,
+ mgcp_bind_trans_net_rtp_port) != 0) {
+ mgcp_rtp_end_reset(&endp->net_end);
+ mgcp_rtp_end_reset(&endp->bts_end);
+ return -1;
+ }
+
+ if (allocate_port(endp, &endp->trans_bts,
+ &endp->cfg->transcoder_ports,
+ mgcp_bind_trans_bts_rtp_port) != 0) {
+ mgcp_rtp_end_reset(&endp->net_end);
+ mgcp_rtp_end_reset(&endp->bts_end);
+ mgcp_rtp_end_reset(&endp->trans_net);
+ return -1;
+ }
+
+ /* remember that we have set up transcoding */
+ endp->type = MGCP_RTP_TRANSCODED;
+ }
+
+ return 0;
+}
+
+/* Set the LCO from a string (see RFC 3435).
+ * The string is stored in the 'string' field. A NULL string is handled excatly
+ * 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);
+}
+
+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_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;
+}
+
+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);
+}
+
+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 *mode = NULL;
+ char *line;
+ int have_sdp = 0, osmux_cid = -1;
+
+ 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 'M':
+ mode = (const char *) line + 3;
+ break;
+ case 'X':
+ /* Osmux is not enabled in this bsc, ignore it so the
+ * bsc-nat knows that we don't want to use Osmux.
+ */
+ 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, "Unhandled option: '%c'/%d on 0x%x\n",
+ *line, *line, ENDPOINT_NUMBER(endp));
+ break;
+ }
+ }
+
+mgcp_header_done:
+ tcfg = p->endp->tcfg;
+
+ /* Check required data */
+ if (!callid || !mode) {
+ LOGP(DLMGCP, LOGL_ERROR, "Missing callid and mode in CRCX on 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+
+ if (endp->allocated) {
+ if (tcfg->force_realloc) {
+ LOGP(DLMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n",
+ ENDPOINT_NUMBER(endp));
+ mgcp_release_endp(endp);
+ if (p->cfg->realloc_cb)
+ p->cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp));
+ } else {
+ LOGP(DLMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ }
+ }
+
+ /* copy some parameters */
+ endp->callid = talloc_strdup(tcfg->endpoints, callid);
+
+ set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
+ local_options);
+
+ if (parse_conn_mode(mode, endp) != 0) {
+ error_code = 517;
+ goto error2;
+ }
+
+ /* initialize */
+ endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0;
+ mgcp_rtp_end_config(endp, 0, &endp->net_end);
+ mgcp_rtp_end_config(endp, 0, &endp->bts_end);
+
+ /* set to zero until we get the info */
+ memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr));
+
+ /* bind to the port now */
+ if (allocate_ports(endp) != 0)
+ goto error2;
+
+ /* assign a local call identifier or fail */
+ endp->ci = generate_call_id(p->cfg);
+ if (endp->ci == CI_UNUSED)
+ goto error2;
+
+ /* Annotate Osmux circuit ID and set it to negotiating state until this
+ * is fully set up from the dummy load.
+ */
+ endp->osmux.state = OSMUX_STATE_DISABLED;
+ if (osmux_cid >= 0) {
+ endp->osmux.cid = osmux_cid;
+ endp->osmux.state = OSMUX_STATE_NEGOTIATING;
+ } else if (endp->cfg->osmux == OSMUX_USAGE_ONLY) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Osmux only and no osmux offered on 0x%x\n", ENDPOINT_NUMBER(endp));
+ goto error2;
+ }
+
+ endp->allocated = 1;
+
+ /* set up RTP media parameters */
+ mgcp_set_audio_info(p->cfg, &endp->bts_end.codec, tcfg->audio_payload, tcfg->audio_name);
+ endp->bts_end.fmtp_extra = talloc_strdup(tcfg->endpoints,
+ tcfg->audio_fmtp_extra);
+ if (have_sdp)
+ mgcp_parse_sdp_data(endp, &endp->net_end, p);
+ else if (endp->local_options.codec)
+ mgcp_set_audio_info(p->cfg, &endp->net_end.codec,
+ PTYPE_UNDEFINED, endp->local_options.codec);
+
+ if (p->cfg->bts_force_ptime) {
+ endp->bts_end.packet_duration_ms = p->cfg->bts_force_ptime;
+ endp->bts_end.force_output_ptime = 1;
+ }
+
+ if (setup_rtp_processing(endp) != 0)
+ 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 rejected by policy on 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ mgcp_release_endp(endp);
+ return create_err_response(endp, 400, "CRCX", p->trans);
+ break;
+ case MGCP_POLICY_DEFER:
+ /* stop processing */
+ create_transcoder(endp);
+ return NULL;
+ break;
+ case MGCP_POLICY_CONT:
+ /* just continue */
+ break;
+ }
+ }
+
+ LOGP(DLMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n",
+ ENDPOINT_NUMBER(endp), endp->ci,
+ endp->net_end.local_port, endp->bts_end.local_port);
+ if (p->cfg->change_cb)
+ p->cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX);
+
+ if (endp->conn_mode & MGCP_CONN_RECV_ONLY && tcfg->keepalive_interval != 0) {
+ send_dummy(endp);
+ }
+
+ create_transcoder(endp);
+ return create_response_with_sdp(endp, "CRCX", p->trans);
+error2:
+ mgcp_release_endp(endp);
+ LOGP(DLMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp));
+ return create_err_response(endp, error_code, "CRCX", p->trans);
+}
+
+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 *local_options = NULL;
+
+ if (p->found != 0)
+ return create_err_response(NULL, 510, "MDCX", p->trans);
+
+ if (endp->ci == CI_UNUSED) {
+ LOGP(DLMGCP, LOGL_ERROR, "Endpoint is not "
+ "holding a connection. 0x%x\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 (verify_call_id(endp, line + 3) != 0)
+ goto error3;
+ break;
+ }
+ case 'I': {
+ if (verify_ci(endp, line + 3) != 0)
+ goto error3;
+ break;
+ }
+ case 'L':
+ local_options = (const char *) line + 3;
+ break;
+ case 'M':
+ if (parse_conn_mode(line + 3, endp) != 0) {
+ error_code = 517;
+ goto error3;
+ }
+ endp->orig_mode = endp->conn_mode;
+ break;
+ case 'Z':
+ silent = strcmp("noanswer", line + 3) == 0;
+ break;
+ case '\0':
+ /* SDP file begins */
+ have_sdp = 1;
+ mgcp_parse_sdp_data(endp, &endp->net_end, p);
+ /* This will exhaust p->save, so the loop will
+ * terminate next time.
+ */
+ break;
+ default:
+ LOGP(DLMGCP, LOGL_NOTICE, "Unhandled MGCP option: '%c'/%d on 0x%x\n",
+ line[0], line[0], ENDPOINT_NUMBER(endp));
+ break;
+ }
+ }
+
+ 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, &endp->net_end.codec,
+ PTYPE_UNDEFINED, endp->local_options.codec);
+
+ if (setup_rtp_processing(endp) != 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 rejected by policy on 0x%x\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, "endp %x MDCX defer\n",
+ ENDPOINT_NUMBER(endp));
+ return NULL;
+ break;
+ case MGCP_POLICY_CONT:
+ /* just continue */
+ break;
+ }
+ }
+
+ mgcp_rtp_end_config(endp, 1, &endp->net_end);
+ mgcp_rtp_end_config(endp, 1, &endp->bts_end);
+
+ /* modify */
+ LOGP(DLMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n",
+ ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+ if (p->cfg->change_cb)
+ p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX);
+
+ if (endp->conn_mode & MGCP_CONN_RECV_ONLY &&
+ endp->tcfg->keepalive_interval != 0)
+ send_dummy(endp);
+
+ if (silent)
+ goto out_silent;
+
+ return create_response_with_sdp(endp, "MDCX", p->trans);
+
+error3:
+ return create_err_response(endp, error_code, "MDCX", p->trans);
+
+
+out_silent:
+ LOGP(DLMGCP, LOGL_DEBUG, "endp %x Modify endpoint: silent exit\n",
+ ENDPOINT_NUMBER(endp));
+ return NULL;
+}
+
+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];
+
+ if (p->found != 0)
+ return create_err_response(NULL, error_code, "DLCX", p->trans);
+
+ if (!p->endp->allocated) {
+ LOGP(DLMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\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 (verify_call_id(endp, line + 3) != 0)
+ goto error3;
+ break;
+ case 'I':
+ if (verify_ci(endp, line + 3) != 0)
+ goto error3;
+ break;
+ case 'Z':
+ silent = strcmp("noanswer", line + 3) == 0;
+ break;
+ default:
+ LOGP(DLMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+ line[0], line[0], ENDPOINT_NUMBER(endp));
+ 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 rejected by policy on 0x%x\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 */
+ delete_transcoder(endp);
+ return NULL;
+ break;
+ case MGCP_POLICY_CONT:
+ /* just continue */
+ break;
+ }
+ }
+
+ /* free the connection */
+ LOGP(DLMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n",
+ ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+
+ /* save the statistics of the current call */
+ mgcp_format_stats(endp, stats, sizeof(stats));
+
+ delete_transcoder(endp);
+ mgcp_release_endp(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:
+ return NULL;
+}
+
+static struct msgb *handle_rsip(struct mgcp_parse_data *p)
+{
+ if (p->found != 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "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;
+
+ 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);
+}
+
+static void mgcp_keepalive_timer_cb(void *_tcfg)
+{
+ struct mgcp_trunk_config *tcfg = _tcfg;
+ int i;
+ LOGP(DLMGCP, LOGL_DEBUG, "Triggered trunk %d keepalive timer.\n",
+ tcfg->trunk_nr);
+
+ if (tcfg->keepalive_interval <= 0)
+ return;
+
+ for (i = 1; i < tcfg->number_endpoints; ++i) {
+ struct mgcp_endpoint *endp = &tcfg->endpoints[i];
+ if (endp->conn_mode == MGCP_CONN_RECV_ONLY)
+ send_dummy(endp);
+ }
+
+ 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);
+}
+
+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->source_port = 2427;
+ cfg->source_addr = talloc_strdup(cfg, "0.0.0.0");
+ cfg->osmux_addr = talloc_strdup(cfg, "0.0.0.0");
+
+ cfg->transcoder_remote_base = 4000;
+
+ cfg->bts_ports.base_port = RTP_PORT_DEFAULT;
+ cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT;
+
+ 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;
+}
+
+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;
+}
+
+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;
+}
+
+static void mgcp_rtp_codec_reset(struct mgcp_rtp_codec *codec)
+{
+ codec->payload_type = -1;
+ talloc_free(codec->subtype_name);
+ codec->subtype_name = NULL;
+ talloc_free(codec->audio_name);
+ 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;
+}
+
+static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end)
+{
+ if (end->local_alloc == PORT_ALLOC_DYNAMIC) {
+ mgcp_free_rtp_port(end);
+ end->local_port = 0;
+ }
+
+ end->packets = 0;
+ end->octets = 0;
+ end->dropped_packets = 0;
+ memset(&end->addr, 0, sizeof(end->addr));
+ end->rtp_port = end->rtcp_port = 0;
+ end->local_alloc = -1;
+ talloc_free(end->fmtp_extra);
+ end->fmtp_extra = NULL;
+ talloc_free(end->rtp_process_data);
+ end->rtp_process_data = 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);
+}
+
+static void mgcp_rtp_end_init(struct mgcp_rtp_end *end)
+{
+ mgcp_rtp_end_reset(end);
+ end->rtp.fd = -1;
+ end->rtcp.fd = -1;
+}
+
+int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg)
+{
+ int i;
+
+ /* Initialize all endpoints */
+ 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) {
+ tcfg->endpoints[i].osmux.allocated_cid = -1;
+ tcfg->endpoints[i].ci = CI_UNUSED;
+ tcfg->endpoints[i].cfg = tcfg->cfg;
+ tcfg->endpoints[i].tcfg = tcfg;
+ mgcp_rtp_end_init(&tcfg->endpoints[i].net_end);
+ mgcp_rtp_end_init(&tcfg->endpoints[i].bts_end);
+ mgcp_rtp_end_init(&tcfg->endpoints[i].trans_net);
+ mgcp_rtp_end_init(&tcfg->endpoints[i].trans_bts);
+ }
+
+ return 0;
+}
+
+void mgcp_release_endp(struct mgcp_endpoint *endp)
+{
+ LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp));
+ endp->ci = CI_UNUSED;
+ endp->allocated = 0;
+
+ 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;
+
+ mgcp_rtp_end_reset(&endp->bts_end);
+ mgcp_rtp_end_reset(&endp->net_end);
+ mgcp_rtp_end_reset(&endp->trans_net);
+ mgcp_rtp_end_reset(&endp->trans_bts);
+ endp->type = MGCP_RTP_DEFAULT;
+
+ memset(&endp->net_state, 0, sizeof(endp->net_state));
+ memset(&endp->bts_state, 0, sizeof(endp->bts_state));
+
+ endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE;
+
+ if (endp->osmux.state == OSMUX_STATE_ENABLED)
+ osmux_disable_endpoint(endp);
+
+ /* release the circuit ID if it had been allocated */
+ osmux_release_cid(endp);
+
+ memset(&endp->taps, 0, sizeof(endp->taps));
+}
+
+void mgcp_initialize_endp(struct mgcp_endpoint *endp)
+{
+ return mgcp_release_endp(endp);
+}
+
+static int send_trans(struct mgcp_config *cfg, const char *buf, int len)
+{
+ struct sockaddr_in addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr = cfg->transcoder_in;
+ addr.sin_port = htons(2427);
+ return sendto(cfg->gw_fd.bfd.fd, buf, len, 0,
+ (struct sockaddr *) &addr, sizeof(addr));
+}
+
+static void send_msg(struct mgcp_endpoint *endp, int endpoint, int port,
+ const char *msg, const char *mode)
+{
+ char buf[2096];
+ int len;
+ int nchars;
+
+ /* hardcoded to AMR right now, we do not know the real type at this point */
+ len = snprintf(buf, sizeof(buf),
+ "%s 42 %x@mgw MGCP 1.0\r\n"
+ "C: 4256\r\n"
+ "M: %s\r\n"
+ "\r\n",
+ msg, endpoint, mode);
+
+ if (len < 0)
+ return;
+
+ nchars = write_response_sdp(endp, buf + len, sizeof(buf) + len - 1, NULL);
+ if (nchars < 0)
+ return;
+
+ len += nchars;
+
+ buf[sizeof(buf) - 1] = '\0';
+
+ send_trans(endp->cfg, buf, len);
+}
+
+static void send_dlcx(struct mgcp_endpoint *endp, int endpoint)
+{
+ char buf[2096];
+ int len;
+
+ len = snprintf(buf, sizeof(buf),
+ "DLCX 43 %x@mgw MGCP 1.0\r\n"
+ "C: 4256\r\n"
+ , endpoint);
+
+ if (len < 0)
+ return;
+
+ buf[sizeof(buf) - 1] = '\0';
+
+ send_trans(endp->cfg, buf, len);
+}
+
+static int send_agent(struct mgcp_config *cfg, const char *buf, int len)
+{
+ return write(cfg->gw_fd.bfd.fd, buf, len);
+}
+
+int mgcp_send_reset_all(struct mgcp_config *cfg)
+{
+ static const char mgcp_reset[] = {
+ "RSIP 1 *@mgw MGCP 1.0\r\n"
+ };
+
+ return send_agent(cfg, mgcp_reset, sizeof mgcp_reset -1);
+}
+
+int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint)
+{
+ char buf[128];
+ int len;
+
+ len = snprintf(buf, sizeof(buf),
+ "RSIP 39 %x@mgw MGCP 1.0\r\n"
+ , endpoint);
+ if (len < 0)
+ return len;
+
+ buf[sizeof(buf) - 1] = '\0';
+
+ return send_agent(endp->cfg, buf, len);
+}
+
+static int setup_rtp_processing(struct mgcp_endpoint *endp)
+{
+ int rc = 0;
+ struct mgcp_config *cfg = endp->cfg;
+
+ if (endp->type != MGCP_RTP_DEFAULT)
+ return 0;
+
+ if (endp->conn_mode == MGCP_CONN_LOOPBACK)
+ return 0;
+
+ if (endp->conn_mode & MGCP_CONN_SEND_ONLY)
+ rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end);
+ else
+ rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, NULL);
+
+ if (endp->conn_mode & MGCP_CONN_RECV_ONLY)
+ rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end);
+ else
+ rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, NULL);
+ return rc;
+}
+
+static void create_transcoder(struct mgcp_endpoint *endp)
+{
+ int port;
+ int in_endp = ENDPOINT_NUMBER(endp);
+ int out_endp = endp_back_channel(in_endp);
+
+ if (endp->type != MGCP_RTP_TRANSCODED)
+ return;
+
+ send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv");
+ send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv");
+ send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv");
+ send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv");
+
+ port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base);
+ endp->trans_bts.rtp_port = htons(port);
+ endp->trans_bts.rtcp_port = htons(port + 1);
+
+ port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base);
+ endp->trans_net.rtp_port = htons(port);
+ endp->trans_net.rtcp_port = htons(port + 1);
+}
+
+static void delete_transcoder(struct mgcp_endpoint *endp)
+{
+ int in_endp = ENDPOINT_NUMBER(endp);
+ int out_endp = endp_back_channel(in_endp);
+
+ if (endp->type != MGCP_RTP_TRANSCODED)
+ return;
+
+ send_dlcx(endp, in_endp);
+ send_dlcx(endp, out_endp);
+}
+
+int mgcp_reset_transcoder(struct mgcp_config *cfg)
+{
+ if (!cfg->transcoder_ip)
+ return 0;
+
+ static const char mgcp_reset[] = {
+ "RSIP 1 13@mgw MGCP 1.0\r\n"
+ };
+
+ return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1);
+}
+
+void mgcp_format_stats(struct mgcp_endpoint *endp, char *msg, size_t size)
+{
+ uint32_t expected, jitter;
+ int ploss;
+ int nchars;
+ mgcp_state_calc_loss(&endp->net_state, &endp->net_end,
+ &expected, &ploss);
+ jitter = mgcp_state_calc_jitter(&endp->net_state);
+
+ nchars = snprintf(msg, size,
+ "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u",
+ endp->bts_end.packets, endp->bts_end.octets,
+ endp->net_end.packets, endp->net_end.octets,
+ ploss, jitter);
+ if (nchars < 0 || nchars >= size)
+ goto truncate;
+
+ msg += nchars;
+ size -= nchars;
+
+ /* Error Counter */
+ nchars = snprintf(msg, size,
+ "\r\nX-Osmo-CP: EC TIS=%u, TOS=%u, TIR=%u, TOR=%u",
+ endp->net_state.in_stream.err_ts_counter,
+ endp->net_state.out_stream.err_ts_counter,
+ endp->bts_state.in_stream.err_ts_counter,
+ endp->bts_state.out_stream.err_ts_counter);
+ if (nchars < 0 || nchars >= size)
+ goto truncate;
+
+ msg += nchars;
+ size -= nchars;
+
+ if (endp->osmux.state == OSMUX_STATE_ENABLED) {
+ snprintf(msg, size,
+ "\r\nX-Osmux-ST: CR=%u, BR=%u",
+ endp->osmux.stats.chunks,
+ endp->osmux.stats.octets);
+ }
+truncate:
+ msg[size - 1] = '\0';
+}
+
+int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os,
+ uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter)
+{
+ char *line, *save;
+ int rc;
+
+ /* initialize with bad values */
+ *ps = *os = *pr = *_or = *jitter = UINT_MAX;
+ *loss = INT_MAX;
+
+
+ line = strtok_r((char *) msg->l2h, "\r\n", &save);
+ if (!line)
+ return -1;
+
+ /* this can only parse the message that is created above... */
+ for_each_non_empty_line(line, save) {
+ switch (line[0]) {
+ case 'P':
+ rc = sscanf(line, "P: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u",
+ ps, os, pr, _or, loss, jitter);
+ return rc == 6 ? 0 : -1;
+ }
+ }
+
+ return -1;
+}
diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c
new file mode 100644
index 0000000..055e1af
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_sdp.c
@@ -0,0 +1,305 @@
+/*
+ * 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/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.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;
+};
+
+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;
+}
+
+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;
+ }
+ }
+}
+
+void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, int payload, 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);
+}
+
+int is_codec_compatible(struct mgcp_endpoint *endp, struct sdp_rtp_map *codec)
+{
+ char *bts_codec;
+ 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.
+ */
+ bts_codec = endp->tcfg->audio_name;
+ if (sscanf(bts_codec, "%63[^/]/%*d/%*d", audio_codec) < 1)
+ return 0;
+
+ return strcasecmp(audio_codec, codec->codec_name) == 0;
+}
+
+int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, 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);
+
+ 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': {
+ int payload;
+ int ptime, ptime2 = 0;
+ char audio_name[64];
+
+
+ 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': {
+ int port, rc;
+
+ 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': {
+ char ipv4[16];
+
+ 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;
+}
+
diff --git a/src/libosmo-mgcp/mgcp_transcode.c b/src/libosmo-mgcp/mgcp_transcode.c
new file mode 100644
index 0000000..cbabdd1
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_transcode.c
@@ -0,0 +1,611 @@
+/*
+ * (C) 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include "g711common.h"
+
+#include <osmocom/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.h>
+#include <osmocom/legacy_mgcp/mgcp_transcode.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/netif/rtp.h>
+
+int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst)
+{
+ struct mgcp_process_rtp_state *state = state_;
+ if (dst)
+ return (nsamples >= 0 ?
+ nsamples / state->dst_samples_per_frame :
+ 1) * state->dst_frame_size;
+ else
+ return (nsamples >= 0 ?
+ nsamples / state->src_samples_per_frame :
+ 1) * state->src_frame_size;
+}
+
+static enum audio_format get_audio_format(const struct mgcp_rtp_codec *codec)
+{
+ if (codec->subtype_name) {
+ if (!strcasecmp("GSM", codec->subtype_name))
+ return AF_GSM;
+ if (!strcasecmp("PCMA", codec->subtype_name))
+ return AF_PCMA;
+ if (!strcasecmp("PCMU", codec->subtype_name))
+ return AF_PCMU;
+#ifdef HAVE_BCG729
+ if (!strcasecmp("G729", codec->subtype_name))
+ return AF_G729;
+#endif
+ if (!strcasecmp("L16", codec->subtype_name))
+ return AF_L16;
+ }
+
+ switch (codec->payload_type) {
+ case 0 /* PCMU */:
+ return AF_PCMU;
+ case 3 /* GSM */:
+ return AF_GSM;
+ case 8 /* PCMA */:
+ return AF_PCMA;
+#ifdef HAVE_BCG729
+ case 18 /* G.729 */:
+ return AF_G729;
+#endif
+ case 11 /* L16 */:
+ return AF_L16;
+ default:
+ return AF_INVALID;
+ }
+}
+
+static void l16_encode(short *sample, unsigned char *buf, size_t n)
+{
+ for (; n > 0; --n, ++sample, buf += 2) {
+ buf[0] = sample[0] >> 8;
+ buf[1] = sample[0] & 0xff;
+ }
+}
+
+static void l16_decode(unsigned char *buf, short *sample, size_t n)
+{
+ for (; n > 0; --n, ++sample, buf += 2)
+ sample[0] = ((short)buf[0] << 8) | buf[1];
+}
+
+static void alaw_encode(short *sample, unsigned char *buf, size_t n)
+{
+ for (; n > 0; --n)
+ *(buf++) = s16_to_alaw(*(sample++));
+}
+
+static void alaw_decode(unsigned char *buf, short *sample, size_t n)
+{
+ for (; n > 0; --n)
+ *(sample++) = alaw_to_s16(*(buf++));
+}
+
+static void ulaw_encode(short *sample, unsigned char *buf, size_t n)
+{
+ for (; n > 0; --n)
+ *(buf++) = s16_to_ulaw(*(sample++));
+}
+
+static void ulaw_decode(unsigned char *buf, short *sample, size_t n)
+{
+ for (; n > 0; --n)
+ *(sample++) = ulaw_to_s16(*(buf++));
+}
+
+static int processing_state_destructor(struct mgcp_process_rtp_state *state)
+{
+ switch (state->src_fmt) {
+ case AF_GSM:
+ if (state->src.gsm_handle)
+ gsm_destroy(state->src.gsm_handle);
+ break;
+#ifdef HAVE_BCG729
+ case AF_G729:
+ if (state->src.g729_dec)
+ closeBcg729DecoderChannel(state->src.g729_dec);
+ break;
+#endif
+ default:
+ break;
+ }
+ switch (state->dst_fmt) {
+ case AF_GSM:
+ if (state->dst.gsm_handle)
+ gsm_destroy(state->dst.gsm_handle);
+ break;
+#ifdef HAVE_BCG729
+ case AF_G729:
+ if (state->dst.g729_enc)
+ closeBcg729EncoderChannel(state->dst.g729_enc);
+ break;
+#endif
+ default:
+ break;
+ }
+ return 0;
+}
+
+int mgcp_transcoding_setup(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end,
+ struct mgcp_rtp_end *src_end)
+{
+ struct mgcp_process_rtp_state *state;
+ enum audio_format src_fmt, dst_fmt;
+ const struct mgcp_rtp_codec *dst_codec = &dst_end->codec;
+
+ /* cleanup first */
+ if (dst_end->rtp_process_data) {
+ talloc_free(dst_end->rtp_process_data);
+ dst_end->rtp_process_data = NULL;
+ }
+
+ if (!src_end)
+ return 0;
+
+ const struct mgcp_rtp_codec *src_codec = &src_end->codec;
+
+ if (endp->tcfg->no_audio_transcoding) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Transcoding disabled on endpoint 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ return 0;
+ }
+
+ src_fmt = get_audio_format(src_codec);
+ dst_fmt = get_audio_format(dst_codec);
+
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Checking transcoding: %s (%d) -> %s (%d)\n",
+ src_codec->subtype_name, src_codec->payload_type,
+ dst_codec->subtype_name, dst_codec->payload_type);
+
+ if (src_fmt == AF_INVALID || dst_fmt == AF_INVALID) {
+ if (!src_codec->subtype_name || !dst_codec->subtype_name)
+ /* Not enough info, do nothing */
+ return 0;
+
+ if (strcasecmp(src_codec->subtype_name, dst_codec->subtype_name) == 0)
+ /* Nothing to do */
+ return 0;
+
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot transcode: %s codec not supported (%s -> %s).\n",
+ src_fmt != AF_INVALID ? "destination" : "source",
+ src_codec->audio_name, dst_codec->audio_name);
+ return -EINVAL;
+ }
+
+ if (src_codec->rate && dst_codec->rate && src_codec->rate != dst_codec->rate) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Cannot transcode: rate conversion (%d -> %d) not supported.\n",
+ src_codec->rate, dst_codec->rate);
+ return -EINVAL;
+ }
+
+ state = talloc_zero(endp->tcfg->cfg, struct mgcp_process_rtp_state);
+ talloc_set_destructor(state, processing_state_destructor);
+ dst_end->rtp_process_data = state;
+
+ state->src_fmt = src_fmt;
+
+ switch (state->src_fmt) {
+ case AF_L16:
+ case AF_S16:
+ state->src_frame_size = 80 * sizeof(short);
+ state->src_samples_per_frame = 80;
+ break;
+ case AF_GSM:
+ state->src_frame_size = sizeof(gsm_frame);
+ state->src_samples_per_frame = 160;
+ state->src.gsm_handle = gsm_create();
+ if (!state->src.gsm_handle) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to initialize GSM decoder.\n");
+ return -EINVAL;
+ }
+ break;
+#ifdef HAVE_BCG729
+ case AF_G729:
+ state->src_frame_size = 10;
+ state->src_samples_per_frame = 80;
+ state->src.g729_dec = initBcg729DecoderChannel();
+ if (!state->src.g729_dec) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to initialize G.729 decoder.\n");
+ return -EINVAL;
+ }
+ break;
+#endif
+ case AF_PCMU:
+ case AF_PCMA:
+ state->src_frame_size = 80;
+ state->src_samples_per_frame = 80;
+ break;
+ default:
+ break;
+ }
+
+ state->dst_fmt = dst_fmt;
+
+ switch (state->dst_fmt) {
+ case AF_L16:
+ case AF_S16:
+ state->dst_frame_size = 80*sizeof(short);
+ state->dst_samples_per_frame = 80;
+ break;
+ case AF_GSM:
+ state->dst_frame_size = sizeof(gsm_frame);
+ state->dst_samples_per_frame = 160;
+ state->dst.gsm_handle = gsm_create();
+ if (!state->dst.gsm_handle) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to initialize GSM encoder.\n");
+ return -EINVAL;
+ }
+ break;
+#ifdef HAVE_BCG729
+ case AF_G729:
+ state->dst_frame_size = 10;
+ state->dst_samples_per_frame = 80;
+ state->dst.g729_enc = initBcg729EncoderChannel();
+ if (!state->dst.g729_enc) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to initialize G.729 decoder.\n");
+ return -EINVAL;
+ }
+ break;
+#endif
+ case AF_PCMU:
+ case AF_PCMA:
+ state->dst_frame_size = 80;
+ state->dst_samples_per_frame = 80;
+ break;
+ default:
+ break;
+ }
+
+ if (dst_end->force_output_ptime)
+ state->dst_packet_duration = mgcp_rtp_packet_duration(endp, dst_end);
+
+ LOGP(DLMGCP, LOGL_INFO,
+ "Initialized RTP processing on: 0x%x "
+ "conv: %d (%d, %d, %s) -> %d (%d, %d, %s)\n",
+ ENDPOINT_NUMBER(endp),
+ src_fmt, src_codec->payload_type, src_codec->rate, src_end->fmtp_extra,
+ dst_fmt, dst_codec->payload_type, dst_codec->rate, dst_end->fmtp_extra);
+
+ return 0;
+}
+
+void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp,
+ int *payload_type,
+ const char**audio_name,
+ const char**fmtp_extra)
+{
+ struct mgcp_process_rtp_state *state = endp->net_end.rtp_process_data;
+ struct mgcp_rtp_codec *net_codec = &endp->net_end.codec;
+ struct mgcp_rtp_codec *bts_codec = &endp->bts_end.codec;
+
+ if (!state || net_codec->payload_type < 0) {
+ *payload_type = bts_codec->payload_type;
+ *audio_name = bts_codec->audio_name;
+ *fmtp_extra = endp->bts_end.fmtp_extra;
+ return;
+ }
+
+ *payload_type = net_codec->payload_type;
+ *audio_name = net_codec->audio_name;
+ *fmtp_extra = endp->net_end.fmtp_extra;
+}
+
+static int decode_audio(struct mgcp_process_rtp_state *state,
+ uint8_t **src, size_t *nbytes)
+{
+ while (*nbytes >= state->src_frame_size) {
+ if (state->sample_cnt + state->src_samples_per_frame > ARRAY_SIZE(state->samples)) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Sample buffer too small: %zu > %zu.\n",
+ state->sample_cnt + state->src_samples_per_frame,
+ ARRAY_SIZE(state->samples));
+ return -ENOSPC;
+ }
+ switch (state->src_fmt) {
+ case AF_GSM:
+ if (gsm_decode(state->src.gsm_handle,
+ (gsm_byte *)*src, state->samples + state->sample_cnt) < 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "Failed to decode GSM.\n");
+ return -EINVAL;
+ }
+ break;
+#ifdef HAVE_BCG729
+ case AF_G729:
+ bcg729Decoder(state->src.g729_dec, *src, 0, state->samples + state->sample_cnt);
+ break;
+#endif
+ case AF_PCMU:
+ ulaw_decode(*src, state->samples + state->sample_cnt,
+ state->src_samples_per_frame);
+ break;
+ case AF_PCMA:
+ alaw_decode(*src, state->samples + state->sample_cnt,
+ state->src_samples_per_frame);
+ break;
+ case AF_S16:
+ memmove(state->samples + state->sample_cnt, *src,
+ state->src_frame_size);
+ break;
+ case AF_L16:
+ l16_decode(*src, state->samples + state->sample_cnt,
+ state->src_samples_per_frame);
+ break;
+ default:
+ break;
+ }
+ *src += state->src_frame_size;
+ *nbytes -= state->src_frame_size;
+ state->sample_cnt += state->src_samples_per_frame;
+ }
+ return 0;
+}
+
+static int encode_audio(struct mgcp_process_rtp_state *state,
+ uint8_t *dst, size_t buf_size, size_t max_samples)
+{
+ int nbytes = 0;
+ size_t nsamples = 0;
+ /* Encode samples into dst */
+ while (nsamples + state->dst_samples_per_frame <= max_samples) {
+ if (nbytes + state->dst_frame_size > buf_size) {
+ if (nbytes > 0)
+ break;
+
+ /* Not even one frame fits into the buffer */
+ LOGP(DLMGCP, LOGL_INFO,
+ "Encoding (RTP) buffer too small: %zu > %zu.\n",
+ nbytes + state->dst_frame_size, buf_size);
+ return -ENOSPC;
+ }
+ switch (state->dst_fmt) {
+ case AF_GSM:
+ gsm_encode(state->dst.gsm_handle,
+ state->samples + state->sample_offs, dst);
+ break;
+#ifdef HAVE_BCG729
+ case AF_G729:
+ bcg729Encoder(state->dst.g729_enc,
+ state->samples + state->sample_offs, dst);
+ break;
+#endif
+ case AF_PCMU:
+ ulaw_encode(state->samples + state->sample_offs, dst,
+ state->src_samples_per_frame);
+ break;
+ case AF_PCMA:
+ alaw_encode(state->samples + state->sample_offs, dst,
+ state->src_samples_per_frame);
+ break;
+ case AF_S16:
+ memmove(dst, state->samples + state->sample_offs,
+ state->dst_frame_size);
+ break;
+ case AF_L16:
+ l16_encode(state->samples + state->sample_offs, dst,
+ state->src_samples_per_frame);
+ break;
+ default:
+ break;
+ }
+ dst += state->dst_frame_size;
+ nbytes += state->dst_frame_size;
+ state->sample_offs += state->dst_samples_per_frame;
+ nsamples += state->dst_samples_per_frame;
+ }
+ state->sample_cnt -= nsamples;
+ return nbytes;
+}
+
+static struct mgcp_rtp_end *source_for_dest(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end)
+{
+ if (&endp->bts_end == dst_end)
+ return &endp->net_end;
+ else if (&endp->net_end == dst_end)
+ return &endp->bts_end;
+ OSMO_ASSERT(0);
+}
+
+/*
+ * With some modems we get offered multiple codecs
+ * and we have selected one of them. It might not
+ * be the right one and we need to detect this with
+ * the first audio packets. One difficulty is that
+ * we patch the rtp payload type in place, so we
+ * need to discuss this.
+ */
+struct mgcp_process_rtp_state *check_transcode_state(
+ struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end,
+ struct rtp_hdr *rtp_hdr)
+{
+ struct mgcp_rtp_end *src_end;
+
+ /* Only deal with messages from net to bts */
+ if (&endp->bts_end != dst_end)
+ goto done;
+
+ src_end = source_for_dest(endp, dst_end);
+
+ /* Already patched */
+ if (rtp_hdr->payload_type == dst_end->codec.payload_type)
+ goto done;
+ /* The payload we expect */
+ if (rtp_hdr->payload_type == src_end->codec.payload_type)
+ goto done;
+ /* The matching alternate payload type? Then switch */
+ if (rtp_hdr->payload_type == src_end->alt_codec.payload_type) {
+ struct mgcp_config *cfg = endp->cfg;
+ struct mgcp_rtp_codec tmp_codec = src_end->alt_codec;
+ src_end->alt_codec = src_end->codec;
+ src_end->codec = tmp_codec;
+ cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end);
+ cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end);
+ }
+
+done:
+ return dst_end->rtp_process_data;
+}
+
+int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp,
+ struct mgcp_rtp_end *dst_end,
+ char *data, int *len, int buf_size)
+{
+ struct mgcp_process_rtp_state *state;
+ const size_t rtp_hdr_size = sizeof(struct rtp_hdr);
+ struct rtp_hdr *rtp_hdr = (struct rtp_hdr *) data;
+ char *payload_data = (char *) &rtp_hdr->data[0];
+ int payload_len = *len - rtp_hdr_size;
+ uint8_t *src = (uint8_t *)payload_data;
+ uint8_t *dst = (uint8_t *)payload_data;
+ size_t nbytes = payload_len;
+ size_t nsamples;
+ size_t max_samples;
+ uint32_t ts_no;
+ int rc;
+
+ state = check_transcode_state(endp, dst_end, rtp_hdr);
+ if (!state)
+ return 0;
+
+ if (state->src_fmt == state->dst_fmt) {
+ if (!state->dst_packet_duration)
+ return 0;
+
+ /* TODO: repackage without transcoding */
+ }
+
+ /* If the remaining samples do not fit into a fixed ptime,
+ * a) discard them, if the next packet is much later
+ * b) add silence and * send it, if the current packet is not
+ * yet too late
+ * c) append the sample data, if the timestamp matches exactly
+ */
+
+ /* TODO: check payload type (-> G.711 comfort noise) */
+
+ if (payload_len > 0) {
+ ts_no = ntohl(rtp_hdr->timestamp);
+ if (!state->is_running) {
+ state->next_seq = ntohs(rtp_hdr->sequence);
+ state->next_time = ts_no;
+ state->is_running = 1;
+ }
+
+
+ if (state->sample_cnt > 0) {
+ int32_t delta = ts_no - state->next_time;
+ /* TODO: check sequence? reordering? packet loss? */
+
+ if (delta > state->sample_cnt) {
+ /* There is a time gap between the last packet
+ * and the current one. Just discard the
+ * partial data that is left in the buffer.
+ * TODO: This can be improved by adding silence
+ * instead if the delta is small enough.
+ */
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "0x%x dropping sample buffer due delta=%d sample_cnt=%zu\n",
+ ENDPOINT_NUMBER(endp), delta, state->sample_cnt);
+ state->sample_cnt = 0;
+ state->next_time = ts_no;
+ } else if (delta < 0) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "RTP time jumps backwards, delta = %d, "
+ "discarding buffered samples\n",
+ delta);
+ state->sample_cnt = 0;
+ state->sample_offs = 0;
+ return -EAGAIN;
+ }
+
+ /* Make sure the samples start without offset */
+ if (state->sample_offs && state->sample_cnt)
+ memmove(&state->samples[0],
+ &state->samples[state->sample_offs],
+ state->sample_cnt *
+ sizeof(state->samples[0]));
+ }
+
+ state->sample_offs = 0;
+
+ /* Append decoded audio to samples */
+ decode_audio(state, &src, &nbytes);
+
+ if (nbytes > 0)
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Skipped audio frame in RTP packet: %zu octets\n",
+ nbytes);
+ } else
+ ts_no = state->next_time;
+
+ if (state->sample_cnt < state->dst_packet_duration)
+ return -EAGAIN;
+
+ max_samples =
+ state->dst_packet_duration ?
+ state->dst_packet_duration : state->sample_cnt;
+
+ nsamples = state->sample_cnt;
+
+ rc = encode_audio(state, dst, buf_size, max_samples);
+ /*
+ * There were no samples to encode?
+ * TODO: how does this work for comfort noise?
+ */
+ if (rc == 0)
+ return -ENOMSG;
+ /* Any other error during the encoding */
+ if (rc < 0)
+ return rc;
+
+ nsamples -= state->sample_cnt;
+
+ *len = rtp_hdr_size + rc;
+ rtp_hdr->sequence = htons(state->next_seq);
+ rtp_hdr->timestamp = htonl(ts_no);
+
+ state->next_seq += 1;
+ state->next_time = ts_no + nsamples;
+
+ /*
+ * XXX: At this point we should always have consumed
+ * samples. So doing OSMO_ASSERT(nsamples > 0) and returning
+ * rtp_hdr_size should be fine.
+ */
+ return nsamples ? rtp_hdr_size : 0;
+}
diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c
new file mode 100644
index 0000000..a48e205
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_vty.c
@@ -0,0 +1,1555 @@
+/* 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/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.h>
+#include <osmocom/legacy_mgcp/vty.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;
+}
+
+/*
+ * vty code for mgcp below
+ */
+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);
+ if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0)
+ vty_out(vty, " bts ip %s%s", g_cfg->bts_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);
+
+ if (g_cfg->bts_ports.mode == PORT_ALLOC_STATIC)
+ vty_out(vty, " rtp bts-base %u%s", g_cfg->bts_ports.base_port, VTY_NEWLINE);
+ else
+ vty_out(vty, " rtp bts-range %u %u%s",
+ g_cfg->bts_ports.range_start, g_cfg->bts_ports.range_end, VTY_NEWLINE);
+ if (g_cfg->bts_ports.bind_addr)
+ vty_out(vty, " rtp bts-bind-ip %s%s", g_cfg->bts_ports.bind_addr, VTY_NEWLINE);
+
+ if (g_cfg->net_ports.mode == PORT_ALLOC_STATIC)
+ vty_out(vty, " rtp net-base %u%s", g_cfg->net_ports.base_port, VTY_NEWLINE);
+ else
+ 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);
+
+ 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->transcoder_ip)
+ vty_out(vty, " transcoder-mgw %s%s", g_cfg->transcoder_ip, VTY_NEWLINE);
+
+ if (g_cfg->transcoder_ports.mode == PORT_ALLOC_STATIC)
+ vty_out(vty, " rtp transcoder-base %u%s", g_cfg->transcoder_ports.base_port, VTY_NEWLINE);
+ else
+ vty_out(vty, " rtp transcoder-range %u %u%s",
+ g_cfg->transcoder_ports.range_start, g_cfg->transcoder_ports.range_end, VTY_NEWLINE);
+ if (g_cfg->bts_force_ptime > 0)
+ vty_out(vty, " rtp force-ptime %d%s", g_cfg->bts_force_ptime, VTY_NEWLINE);
+ vty_out(vty, " transcoder-remote-base %u%s", g_cfg->transcoder_remote_base, 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(const char *end_name, struct vty *vty,
+ struct mgcp_rtp_state *state, struct mgcp_rtp_end *end)
+{
+ struct mgcp_rtp_codec *codec = &end->codec;
+
+ vty_out(vty,
+ " %s%s"
+ " 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",
+ end_name, VTY_NEWLINE,
+ 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;
+
+ 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: CI: %d net: %u/%u bts: %u/%u on %s "
+ "traffic received bts: %u remote: %u transcoder: %u/%u%s",
+ i, endp->ci,
+ ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port),
+ ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port),
+ inet_ntoa(endp->bts_end.addr),
+ endp->bts_end.packets, endp->net_end.packets,
+ endp->trans_net.packets, endp->trans_bts.packets,
+ VTY_NEWLINE);
+
+ if (verbose && endp->allocated) {
+ dump_rtp_end("Net->BTS", vty, &endp->bts_state, &endp->bts_end);
+ dump_rtp_end("BTS->Net", vty, &endp->net_state, &endp->net_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;
+}
+
+DEFUN(cfg_mgcp_bts_ip,
+ cfg_mgcp_bts_ip_cmd,
+ "bts ip A.B.C.D",
+ "BTS Audio source/destination options\n"
+ IP_STR
+ "IPv4 Address of the BTS\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ip, argv[0]);
+ inet_aton(g_cfg->bts_ip, &g_cfg->bts_in);
+ 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_base(struct mgcp_port_range *range, const char **argv)
+{
+ unsigned int port = atoi(argv[0]);
+ range->mode = PORT_ALLOC_STATIC;
+ range->base_port = port;
+}
+
+static void parse_range(struct mgcp_port_range *range, const char **argv)
+{
+ range->mode = PORT_ALLOC_DYNAMIC;
+ range->range_start = atoi(argv[0]);
+ range->range_end = atoi(argv[1]);
+ range->last_port = g_cfg->bts_ports.range_start;
+}
+
+
+#define RTP_STR "RTP configuration\n"
+#define BTS_START_STR "First UDP port allocated for the BTS side\n"
+#define NET_START_STR "First UDP port allocated for the NET side\n"
+#define UDP_PORT_STR "UDP Port number\n"
+DEFUN(cfg_mgcp_rtp_bts_base_port,
+ cfg_mgcp_rtp_bts_base_port_cmd,
+ "rtp bts-base <0-65534>",
+ RTP_STR
+ BTS_START_STR
+ UDP_PORT_STR)
+{
+ parse_base(&g_cfg->bts_ports, argv);
+ return CMD_SUCCESS;
+}
+
+#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_bts_range,
+ cfg_mgcp_rtp_bts_range_cmd,
+ "rtp bts-range <0-65534> <0-65534>",
+ RTP_STR "Range of ports to use for the BTS side\n"
+ RANGE_START_STR RANGE_END_STR)
+{
+ parse_range(&g_cfg->bts_ports, argv);
+ return CMD_SUCCESS;
+}
+
+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_base_port,
+ cfg_mgcp_rtp_net_base_port_cmd,
+ "rtp net-base <0-65534>",
+ RTP_STR NET_START_STR UDP_PORT_STR)
+{
+ parse_base(&g_cfg->net_ports, argv);
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_rtp_bts_base_port, cfg_mgcp_rtp_base_port_cmd,
+ "rtp base <0-65534>",
+ RTP_STR BTS_START_STR UDP_PORT_STR)
+
+DEFUN(cfg_mgcp_rtp_transcoder_range,
+ cfg_mgcp_rtp_transcoder_range_cmd,
+ "rtp transcoder-range <0-65534> <0-65534>",
+ RTP_STR "Range of ports to use for the Transcoder\n"
+ RANGE_START_STR RANGE_END_STR)
+{
+ parse_range(&g_cfg->transcoder_ports, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_transcoder_base,
+ cfg_mgcp_rtp_transcoder_base_cmd,
+ "rtp transcoder-base <0-65534>",
+ RTP_STR "First UDP port allocated for the Transcoder side\n"
+ UDP_PORT_STR)
+{
+ parse_base(&g_cfg->transcoder_ports, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_bts_bind_ip,
+ cfg_mgcp_rtp_bts_bind_ip_cmd,
+ "rtp bts-bind-ip A.B.C.D",
+ RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ports.bind_addr, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_no_bts_bind_ip,
+ cfg_mgcp_rtp_no_bts_bind_ip_cmd,
+ "no rtp bts-bind-ip",
+ NO_STR RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n")
+{
+ talloc_free(g_cfg->bts_ports.bind_addr);
+ g_cfg->bts_ports.bind_addr = NULL;
+ 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_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 to the BTS"
+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->bts_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->bts_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_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, 0);
+ 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_transcoder,
+ cfg_mgcp_transcoder_cmd,
+ "transcoder-mgw A.B.C.D",
+ "Use a MGW to detranscoder RTP\n"
+ "The IP address of the MGW")
+{
+ osmo_talloc_replace_string(g_cfg, &g_cfg->transcoder_ip, argv[0]);
+ inet_aton(g_cfg->transcoder_ip, &g_cfg->transcoder_in);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_transcoder,
+ cfg_mgcp_no_transcoder_cmd,
+ "no transcoder-mgw",
+ NO_STR "Disable the transcoding\n")
+{
+ if (g_cfg->transcoder_ip) {
+ LOGP(DLMGCP, LOGL_NOTICE, "Disabling transcoding on future calls.\n");
+ talloc_free(g_cfg->transcoder_ip);
+ g_cfg->transcoder_ip = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_transcoder_remote_base,
+ cfg_mgcp_transcoder_remote_base_cmd,
+ "transcoder-remote-base <0-65534>",
+ "Set the base port for the transcoder\n" "The RTP base port on the transcoder")
+{
+ g_cfg->transcoder_remote_base = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+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);
+ 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_endp,
+ loop_endp_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;
+
+ 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]);
+
+ if (loop)
+ endp->conn_mode = MGCP_CONN_LOOPBACK;
+ else
+ endp->conn_mode = endp->orig_mode;
+
+ /* Handle it like a MDCX, switch on SSRC patching if enabled */
+ mgcp_rtp_end_config(endp, 1, &endp->bts_end);
+ mgcp_rtp_end_config(endp, 1, &endp->net_end);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(tap_call,
+ tap_call_cmd,
+ "tap-call <0-64> ENDPOINT (bts-in|bts-out|net-in|net-out) A.B.C.D <0-65534>",
+ "Forward data on endpoint to a different system\n" "Trunk number\n"
+ "The endpoint in hex\n"
+ "Forward the data coming from the bts\n"
+ "Forward the data coming from the bts leaving to the network\n"
+ "Forward the data coming from the net\n"
+ "Forward the data coming from the net leaving to the bts\n"
+ "destination IP of the data\n" "destination port\n")
+{
+ struct mgcp_rtp_tap *tap;
+ struct mgcp_trunk_config *trunk;
+ struct mgcp_endpoint *endp;
+ int port = 0;
+
+ 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];
+
+ if (strcmp(argv[2], "bts-in") == 0) {
+ port = MGCP_TAP_BTS_IN;
+ } else if (strcmp(argv[2], "bts-out") == 0) {
+ port = MGCP_TAP_BTS_OUT;
+ } else if (strcmp(argv[2], "net-in") == 0) {
+ port = MGCP_TAP_NET_IN;
+ } else if (strcmp(argv[2], "net-out") == 0) {
+ port = MGCP_TAP_NET_OUT;
+ } else {
+ vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ tap = &endp->taps[port];
+ memset(&tap->forward, 0, sizeof(tap->forward));
+ inet_aton(argv[3], &tap->forward.sin_addr);
+ tap->forward.sin_port = htons(atoi(argv[4]));
+ 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;
+ }
+
+ 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_endp_cmd);
+ install_element(ENABLE_NODE, &tap_call_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);
+
+ vty_install_default(MGCP_NODE);
+ install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_bts_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_base_port_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_base_port_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_net_base_port_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_range_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_bind_ip_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_no_bts_bind_ip_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_transcoder_range_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_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_transcoder_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_no_transcoder_cmd);
+ install_element(MGCP_NODE, &cfg_mgcp_transcoder_remote_base_cmd);
+ 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_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);
+ vty_install_default(TRUNK_NODE);
+ 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)
+{
+ int i;
+ struct mgcp_config *cfg = trunk->cfg;
+
+ 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;
+ }
+
+ /* early bind */
+ for (i = 1; i < trunk->number_endpoints; ++i) {
+ struct mgcp_endpoint *endp = &trunk->endpoints[i];
+
+ if (cfg->bts_ports.mode == PORT_ALLOC_STATIC) {
+ cfg->last_bts_port += 2;
+ if (mgcp_bind_bts_rtp_port(endp, cfg->last_bts_port) != 0) {
+ LOGP(DLMGCP, LOGL_FATAL,
+ "Failed to bind: %d\n", cfg->last_bts_port);
+ return -1;
+ }
+ endp->bts_end.local_alloc = PORT_ALLOC_STATIC;
+ }
+
+ if (cfg->net_ports.mode == PORT_ALLOC_STATIC) {
+ cfg->last_net_port += 2;
+ if (mgcp_bind_net_rtp_port(endp, cfg->last_net_port) != 0) {
+ LOGP(DLMGCP, LOGL_FATAL,
+ "Failed to bind: %d\n", cfg->last_net_port);
+ return -1;
+ }
+ endp->net_end.local_alloc = PORT_ALLOC_STATIC;
+ }
+
+ if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL &&
+ cfg->transcoder_ip && cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) {
+ int rtp_port;
+
+ /* network side */
+ rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp),
+ cfg->transcoder_ports.base_port);
+ if (mgcp_bind_trans_net_rtp_port(endp, rtp_port) != 0) {
+ LOGP(DLMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+ return -1;
+ }
+ endp->trans_net.local_alloc = PORT_ALLOC_STATIC;
+
+ /* bts side */
+ rtp_port = rtp_calculate_port(endp_back_channel(ENDPOINT_NUMBER(endp)),
+ cfg->transcoder_ports.base_port);
+ if (mgcp_bind_trans_bts_rtp_port(endp, rtp_port) != 0) {
+ LOGP(DLMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+ return -1;
+ }
+ endp->trans_bts.local_alloc = PORT_ALLOC_STATIC;
+ }
+ }
+
+ 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->bts_ip)
+ fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n");
+
+ if (!g_cfg->source_addr) {
+ fprintf(stderr, "You need to specify a bind address.\n");
+ return -1;
+ }
+
+ /* initialize the last ports */
+ g_cfg->last_bts_port = rtp_calculate_port(0, g_cfg->bts_ports.base_port);
+ g_cfg->last_net_port = rtp_calculate_port(0, g_cfg->net_ports.base_port);
+
+ 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;
+}
+
diff --git a/src/osmo-mgw/Makefile.am b/src/osmo-mgw/Makefile.am
new file mode 100644
index 0000000..3e3511d
--- /dev/null
+++ b/src/osmo-mgw/Makefile.am
@@ -0,0 +1,31 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBBCG729_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+bin_PROGRAMS = \
+ osmo-bsc_mgcp \
+ $(NULL)
+
+osmo_bsc_mgcp_SOURCES = \
+ mgcp_main.c \
+ $(NULL)
+
+osmo_bsc_mgcp_LDADD = \
+ $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBBCG729_LIBS) \
+ $(LIBRARY_GSM) \
+ $(NULL)
diff --git a/src/osmo-mgw/mgcp_main.c b/src/osmo-mgw/mgcp_main.c
new file mode 100644
index 0000000..48241a6
--- /dev/null
+++ b/src/osmo-mgw/mgcp_main.c
@@ -0,0 +1,351 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The main method to drive it as a standalone process */
+
+/*
+ * (C) 2009-2011 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+
+#include <osmocom/legacy_mgcp/mgcp.h>
+#include <osmocom/legacy_mgcp/mgcp_internal.h>
+#include <osmocom/legacy_mgcp/vty.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/stats.h>
+
+#include "../../bscconfig.h"
+
+#ifdef BUILD_MGCP_TRANSCODING
+#include <osmocom/legacy_mgcp/mgcp_transcode.h>
+#endif
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#warning "Make use of the rtp proxy code"
+
+static struct mgcp_config *cfg;
+static struct mgcp_trunk_config *reset_trunk;
+static int reset_endpoints = 0;
+static int daemonize = 0;
+
+const char *openbsc_copyright =
+ "Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n"
+ "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+ "Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n"
+ "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static char *config_file = "mgcp.cfg";
+
+/* used by msgb and mgcp */
+void *tall_bsc_ctx = NULL;
+
+static void print_help()
+{
+ printf("Some useful help...\n");
+ printf(" -h --help is printing this text.\n");
+ printf(" -c --config-file filename The config file to use.\n");
+ printf(" -s --disable-color\n");
+ printf(" -D --daemonize Fork the process into a background daemon\n");
+ printf(" -V --version Print the version number\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"config-file", 1, 0, 'c'},
+ {"daemonize", 0, 0, 'D'},
+ {"version", 0, 0, 'V'},
+ {"disable-color", 0, 0, 's'},
+ {0, 0, 0, 0},
+ };
+
+ c = getopt_long(argc, argv, "hc:VD", long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch(c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 'c':
+ config_file = talloc_strdup(tall_bsc_ctx, optarg);
+ break;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ default:
+ /* ignore */
+ break;
+ };
+ }
+}
+
+/* simply remember this */
+static int mgcp_rsip_cb(struct mgcp_trunk_config *tcfg)
+{
+ reset_endpoints = 1;
+ reset_trunk = tcfg;
+
+ return 0;
+}
+
+static int read_call_agent(struct osmo_fd *fd, unsigned int what)
+{
+ struct sockaddr_in addr;
+ socklen_t slen = sizeof(addr);
+ struct msgb *msg;
+ struct msgb *resp;
+ int i;
+
+ msg = (struct msgb *) fd->data;
+
+ /* read one less so we can use it as a \0 */
+ int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0,
+ (struct sockaddr *) &addr, &slen);
+ if (rc < 0) {
+ perror("Gateway failed to read");
+ return -1;
+ } else if (slen > sizeof(addr)) {
+ fprintf(stderr, "Gateway received message from outerspace: %zu %zu\n",
+ (size_t) slen, sizeof(addr));
+ return -1;
+ }
+
+ /* handle message now */
+ msg->l2h = msgb_put(msg, rc);
+ resp = mgcp_handle_message(cfg, msg);
+ msgb_reset(msg);
+
+ if (resp) {
+ sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr));
+ msgb_free(resp);
+ }
+
+ if (reset_endpoints) {
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Asked to reset endpoints: %d/%d\n",
+ reset_trunk->trunk_nr, reset_trunk->trunk_type);
+ reset_endpoints = 0;
+
+ /* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */
+ for (i = 1; i < reset_trunk->number_endpoints; ++i)
+ mgcp_release_endp(&reset_trunk->endpoints[i]);
+ }
+
+ return 0;
+}
+
+int mgcp_vty_is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case CONFIG_NODE:
+ return 0;
+
+ default:
+ return 1;
+ }
+}
+
+int mgcp_vty_go_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case TRUNK_NODE:
+ vty->node = MGCP_NODE;
+ vty->index = NULL;
+ break;
+ case MGCP_NODE:
+ default:
+ if (mgcp_vty_is_config_node(vty, vty->node))
+ vty->node = CONFIG_NODE;
+ else
+ vty->node = ENABLE_NODE;
+
+ vty->index = NULL;
+ }
+
+ return vty->node;
+}
+
+
+static struct vty_app_info vty_info = {
+ .name = "OpenBSC MGCP",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = mgcp_vty_go_parent,
+ .is_config_node = mgcp_vty_is_config_node,
+};
+
+static const struct log_info_cat log_categories[] = {
+ /* DLMGCP is provided by the MGCP library */
+};
+
+const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ struct sockaddr_in addr;
+ int on = 1, rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+ osmo_init_ignore_signals();
+ osmo_init_logging(&log_info);
+
+ cfg = mgcp_config_alloc();
+ if (!cfg)
+ return -1;
+
+#ifdef BUILD_MGCP_TRANSCODING
+ cfg->setup_rtp_processing_cb = &mgcp_transcoding_setup;
+ cfg->rtp_processing_cb = &mgcp_transcoding_process_rtp;
+ cfg->get_net_downlink_format_cb = &mgcp_transcoding_net_downlink_format;
+#endif
+
+ cfg->trunk.force_realloc = 1;
+
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds(NULL);
+ osmo_stats_vty_add_cmds(&log_info);
+ mgcp_vty_init();
+
+ handle_options(argc, argv);
+
+ rate_ctr_init(tall_bsc_ctx);
+ osmo_stats_init(tall_bsc_ctx);
+
+ rc = mgcp_parse_config(config_file, cfg, MGCP_BSC);
+ if (rc < 0)
+ return rc;
+
+ /* start telnet after reading config for vty_get_bind_addr() */
+ rc = telnet_init_dynif(tall_bsc_ctx, NULL,
+ vty_get_bind_addr(), OSMO_VTY_PORT_BSC_MGCP);
+ if (rc < 0)
+ return rc;
+
+ /* set some callbacks */
+ cfg->reset_cb = mgcp_rsip_cb;
+
+ /* we need to bind a socket */
+ if (rc == 0) {
+ cfg->gw_fd.bfd.when = BSC_FD_READ;
+ cfg->gw_fd.bfd.cb = read_call_agent;
+ cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (cfg->gw_fd.bfd.fd < 0) {
+ perror("Gateway failed to listen");
+ return -1;
+ }
+
+ setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(cfg->source_port);
+ inet_aton(cfg->source_addr, &addr.sin_addr);
+
+ if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ perror("Gateway failed to bind");
+ return -1;
+ }
+
+ cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg");
+ if (!cfg->gw_fd.bfd.data) {
+ fprintf(stderr, "Gateway memory error.\n");
+ return -1;
+ }
+
+ if (cfg->call_agent_addr) {
+ addr.sin_port = htons(2727);
+ inet_aton(cfg->call_agent_addr, &addr.sin_addr);
+ if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n",
+ cfg->call_agent_addr, errno);
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+ }
+
+ if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) {
+ LOGP(DLMGCP, LOGL_FATAL, "Failed to register the fd\n");
+ return -1;
+ }
+
+ LOGP(DLMGCP, LOGL_NOTICE, "Configured for MGCP.\n");
+ }
+
+ /* initialisation */
+ srand(time(NULL));
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ /* main loop */
+ while (1) {
+ osmo_select_main(0);
+ }
+
+
+ return 0;
+}