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