summaryrefslogtreecommitdiff
path: root/src/libosmo-mgcp
diff options
context:
space:
mode:
authorRuben Undheim <ruben.undheim@gmail.com>2018-11-13 08:11:52 +0100
committerRuben Undheim <ruben.undheim@gmail.com>2018-11-13 08:11:52 +0100
commitd45a0642eed2a5c42fe5c9387d66808bc2f78b0b (patch)
tree06036739d32af94b30f1da909d05107b5acb363d /src/libosmo-mgcp
parent97e4319456051c61aff9b43ac2eec420acb81d21 (diff)
New upstream version 1.4.0
Diffstat (limited to 'src/libosmo-mgcp')
-rw-r--r--src/libosmo-mgcp/Makefile.am1
-rw-r--r--src/libosmo-mgcp/mgcp_codec.c343
-rw-r--r--src/libosmo-mgcp/mgcp_conn.c54
-rw-r--r--src/libosmo-mgcp/mgcp_network.c75
-rw-r--r--src/libosmo-mgcp/mgcp_osmux.c35
-rw-r--r--src/libosmo-mgcp/mgcp_protocol.c301
-rw-r--r--src/libosmo-mgcp/mgcp_sdp.c258
-rw-r--r--src/libosmo-mgcp/mgcp_stat.c31
-rw-r--r--src/libosmo-mgcp/mgcp_vty.c63
9 files changed, 865 insertions, 296 deletions
diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am
index a98c391..587bdd4 100644
--- a/src/libosmo-mgcp/Makefile.am
+++ b/src/libosmo-mgcp/Makefile.am
@@ -35,6 +35,7 @@ libosmo_mgcp_a_SOURCES = \
mgcp_vty.c \
mgcp_osmux.c \
mgcp_sdp.c \
+ mgcp_codec.c \
mgcp_msg.c \
mgcp_conn.c \
mgcp_stat.c \
diff --git a/src/libosmo-mgcp/mgcp_codec.c b/src/libosmo-mgcp/mgcp_codec.c
new file mode 100644
index 0000000..2ce90dd
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_codec.c
@@ -0,0 +1,343 @@
+/*
+ * (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/mgcp/mgcp_internal.h>
+#include <osmocom/mgcp/mgcp_endp.h>
+#include <errno.h>
+
+/* Helper function to dump codec information of a specified codec to a printable
+ * string, used by dump_codec_summary() */
+static char *dump_codec(struct mgcp_rtp_codec *codec)
+{
+ static char str[256];
+ char *pt_str;
+
+ if (codec->payload_type > 76)
+ pt_str = "DYNAMIC";
+ else if (codec->payload_type > 72)
+ pt_str = "RESERVED <!>";
+ else if (codec->payload_type != PTYPE_UNDEFINED)
+ pt_str = codec->subtype_name;
+ else
+ pt_str = "INVALID <!>";
+
+ snprintf(str, sizeof(str), "(pt:%i=%s, audio:%s subt=%s, rate=%u, ch=%i, t=%u/%u)", codec->payload_type, pt_str,
+ codec->audio_name, codec->subtype_name, codec->rate, codec->channels, codec->frame_duration_num,
+ codec->frame_duration_den);
+ return str;
+}
+
+/*! Dump a summary of all negotiated codecs to debug log
+ * \param[in] conn related rtp-connection. */
+void mgcp_codec_summary(struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_rtp_end *rtp;
+ unsigned int i;
+ struct mgcp_rtp_codec *codec;
+ struct mgcp_endpoint *endp;
+
+ rtp = &conn->end;
+ endp = conn->conn->endp;
+
+ if (rtp->codecs_assigned == 0) {
+ LOGP(DLMGCP, LOGL_ERROR, "endpoint:0x%x conn:%s no codecs available\n", ENDPOINT_NUMBER(endp),
+ mgcp_conn_dump(conn->conn));
+ return;
+ }
+
+ /* Store parsed codec information */
+ for (i = 0; i < rtp->codecs_assigned; i++) {
+ codec = &rtp->codecs[i];
+
+ LOGP(DLMGCP, LOGL_DEBUG, "endpoint:0x%x conn:%s codecs[%u]:%s", ENDPOINT_NUMBER(endp),
+ mgcp_conn_dump(conn->conn), i, dump_codec(codec));
+
+ if (codec == rtp->codec)
+ LOGPC(DLMGCP, LOGL_DEBUG, " [selected]");
+
+ LOGPC(DLMGCP, LOGL_DEBUG, "\n");
+ }
+}
+
+/* Initalize or reset codec information with default data. */
+void codec_init(struct mgcp_rtp_codec *codec)
+{
+ if (codec->subtype_name)
+ talloc_free(codec->subtype_name);
+ if (codec->audio_name)
+ talloc_free(codec->audio_name);
+ memset(codec, 0, sizeof(*codec));
+ codec->payload_type = -1;
+ 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;
+}
+
+/*! Initalize or reset codec information with default data.
+ * \param[out] conn related rtp-connection. */
+void mgcp_codec_reset_all(struct mgcp_conn_rtp *conn)
+{
+ memset(conn->end.codecs, 0, sizeof(conn->end.codecs));
+ conn->end.codecs_assigned = 0;
+ conn->end.codec = NULL;
+}
+
+/* Set members of struct mgcp_rtp_codec, extrapolate in missing information */
+static int codec_set(void *ctx, struct mgcp_rtp_codec *codec,
+ int payload_type, const char *audio_name, unsigned int pt_offset)
+{
+ int rate;
+ int channels;
+ char audio_codec[64];
+
+ /* Initalize the codec struct with some default data to begin with */
+ codec_init(codec);
+
+ if (payload_type != PTYPE_UNDEFINED) {
+ /* Make sure we do not get any reserved or undefined type numbers */
+ /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
+ if (payload_type == 1 || payload_type == 2 || payload_type == 19)
+ goto error;
+ if (payload_type >= 72 && payload_type <= 76)
+ goto error;
+ if (payload_type >= 127)
+ goto error;
+
+ codec->payload_type = payload_type;
+ }
+
+ /* When no audio name is given, we are forced to use the payload
+ * type to generate the audio name. This is only possible for
+ * non dynamic payload types, which are statically defined */
+ if (!audio_name) {
+ switch (payload_type) {
+ case 0:
+ audio_name = talloc_strdup(ctx, "PCMU/8000/1");
+ break;
+ case 3:
+ audio_name = talloc_strdup(ctx, "GSM/8000/1");
+ break;
+ case 8:
+ audio_name = talloc_strdup(ctx, "PCMA/8000/1");
+ break;
+ case 18:
+ audio_name = talloc_strdup(ctx, "G729/8000/1");
+ break;
+ default:
+ /* The given payload type is not known to us, or it
+ * it is a dynamic payload type for which we do not
+ * know the audio name. We must give up here */
+ goto error;
+ }
+ }
+
+ /* Now we extract the codec subtype name, rate and channels. The latter
+ * two are optional. If they are not present we use the safe defaults
+ * above. */
+ if (strlen(audio_name) > sizeof(audio_codec))
+ goto error;
+ channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS;
+ rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
+ if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1)
+ goto error;
+
+ /* Note: We only accept configurations with one audio channel! */
+ if (channels != 1)
+ goto error;
+
+ codec->rate = rate;
+ codec->channels = channels;
+ codec->subtype_name = talloc_strdup(ctx, audio_codec);
+ codec->audio_name = talloc_strdup(ctx, audio_name);
+ codec->payload_type = payload_type;
+
+ 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;
+ }
+
+ /* Derive the payload type if it is unknown */
+ if (codec->payload_type == PTYPE_UNDEFINED) {
+
+ /* For the known codecs from the static range we restore
+ * the IANA or 3GPP assigned payload type number */
+ if (codec->rate == 8000 && codec->channels == 1) {
+ /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
+ if (!strcmp(codec->subtype_name, "GSM"))
+ codec->payload_type = 3;
+ else if (!strcmp(codec->subtype_name, "PCMA"))
+ codec->payload_type = 8;
+ else if (!strcmp(codec->subtype_name, "PCMU"))
+ codec->payload_type = 0;
+ else if (!strcmp(codec->subtype_name, "G729"))
+ codec->payload_type = 18;
+
+ /* See also: 3GPP TS 48.103, chapter 5.4.2.2 RTP Payload
+ * Note: These are not fixed payload types as the IANA
+ * defined once, they still remain dymanic payload
+ * types, but with a payload type number preference. */
+ else if (!strcmp(codec->subtype_name, "GSM-EFR"))
+ codec->payload_type = 110;
+ else if (!strcmp(codec->subtype_name, "GSM-HR-08"))
+ codec->payload_type = 111;
+ else if (!strcmp(codec->subtype_name, "AMR"))
+ codec->payload_type = 112;
+ else if (!strcmp(codec->subtype_name, "AMR-WB"))
+ codec->payload_type = 113;
+ }
+
+ /* If we could not determine a payload type we assume that
+ * we are dealing with a codec from the dynamic range. We
+ * choose a fixed identifier from 96-109. (Note: normally,
+ * the dynamic payload type rante is from 96-127, but from
+ * 110 onwards 3gpp defines prefered codec types, which are
+ * also fixed, see above) */
+ if (codec->payload_type < 0) {
+ codec->payload_type = 96 + pt_offset;
+ if (codec->payload_type > 109)
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ /* Make sure we leave a clean codec entry on error. */
+ codec_init(codec);
+ memset(codec, 0, sizeof(*codec));
+ return -EINVAL;
+}
+
+/*! Add codec configuration depending on payload type and/or codec name. This
+ * function uses the input parameters to extrapolate the full codec information.
+ * \param[out] codec configuration (caller provided memory).
+ * \param[out] conn related rtp-connection.
+ * \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, -EINVAL on failure. */
+int mgcp_codec_add(struct mgcp_conn_rtp *conn, int payload_type, const char *audio_name)
+{
+ int rc;
+
+ /* The amount of codecs we can store is limited, make sure we do not
+ * overrun this limit. */
+ if (conn->end.codecs_assigned >= MGCP_MAX_CODECS)
+ return -EINVAL;
+
+ rc = codec_set(conn->conn, &conn->end.codecs[conn->end.codecs_assigned], payload_type, audio_name,
+ conn->end.codecs_assigned);
+ if (rc != 0)
+ return -EINVAL;
+
+ conn->end.codecs_assigned++;
+
+ return 0;
+}
+
+/* Check if the given codec is applicable on the specified endpoint
+ * Helper function for mgcp_codec_decide() */
+static bool is_codec_compatible(const struct mgcp_endpoint *endp, const struct mgcp_rtp_codec *codec)
+{
+ char codec_name[64];
+
+ /* A codec name must be set, if not, this might mean that the codec
+ * (payload type) that was assigned is unknown to us so we must stop
+ * here. */
+ if (!codec->subtype_name)
+ return false;
+
+ /* We now extract the codec_name (letters before the /, e.g. "GSM"
+ * from the audio name that is stored in the trunk configuration.
+ * We do not compare to the full audio_name because we expect that
+ * "GSM", "GSM/8000" and "GSM/8000/1" are all compatible when the
+ * audio name of the codec is set to "GSM" */
+ if (sscanf(endp->tcfg->audio_name, "%63[^/]/%*d/%*d", codec_name) < 1)
+ return false;
+
+ /* Finally we check if the subtype_name we have generated from the
+ * audio_name in the trunc struct patches the codec_name of the
+ * given codec */
+ if (strcasecmp(codec_name, codec->subtype_name) == 0)
+ return true;
+
+ /* FIXME: It is questinable that the method to pick a compatible
+ * codec can work properly. Since this useses tcfg->audio_name, as
+ * a reference, which is set to "AMR/8000" permanently.
+ * tcfg->audio_name must be updated by the first connection that
+ * has been made on an endpoint, so that the second connection
+ * can make a meaningful decision here */
+
+ return false;
+}
+
+/*! Decide for one suitable codec
+ * \param[in] conn related rtp-connection.
+ * \returns 0 on success, -EINVAL on failure. */
+int mgcp_codec_decide(struct mgcp_conn_rtp *conn)
+{
+ struct mgcp_rtp_end *rtp;
+ unsigned int i;
+ struct mgcp_endpoint *endp;
+ bool codec_assigned = false;
+
+ endp = conn->conn->endp;
+ rtp = &conn->end;
+
+ /* This function works on the results the SDP/LCO parser has extracted
+ * from the MGCP message. The goal is to select a suitable codec for
+ * the given connection. When transcoding is available, the first codec
+ * from the codec list is taken without further checking. When
+ * transcoding is not available, then the choice must be made more
+ * carefully. Each codec in the list is checked until one is found that
+ * is rated compatible. The rating is done by the helper function
+ * is_codec_compatible(), which does the actual checking. */
+ for (i = 0; i < rtp->codecs_assigned; i++) {
+ /* When no transcoding is available, avoid codecs that would
+ * require transcoding. */
+ if (endp->tcfg->no_audio_transcoding && !is_codec_compatible(endp, &rtp->codecs[i])) {
+ LOGP(DLMGCP, LOGL_NOTICE, "transcoding not available, skipping codec: %d/%s\n",
+ rtp->codecs[i].payload_type, rtp->codecs[i].subtype_name);
+ continue;
+ }
+
+ rtp->codec = &rtp->codecs[i];
+ codec_assigned = true;
+ break;
+ }
+
+ /* FIXME: To the reviewes: This is problematic. I do not get why we
+ * need to reset the packet_duration_ms depending on the codec
+ * selection. I thought it were all 20ms? Is this to address some
+ * cornercase. (This piece of code was in the code path before,
+ * together with the note: "TODO/XXX: Store this per codec and derive
+ * it on use" */
+ if (codec_assigned) {
+ if (rtp->maximum_packet_time >= 0
+ && rtp->maximum_packet_time * rtp->codec->frame_duration_den >
+ rtp->codec->frame_duration_num * 1500)
+ rtp->packet_duration_ms = 0;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
diff --git a/src/libosmo-mgcp/mgcp_conn.c b/src/libosmo-mgcp/mgcp_conn.c
index 998dbc5..3a5db0f 100644
--- a/src/libosmo-mgcp/mgcp_conn.c
+++ b/src/libosmo-mgcp/mgcp_conn.c
@@ -25,9 +25,31 @@
#include <osmocom/mgcp/mgcp_internal.h>
#include <osmocom/mgcp/mgcp_common.h>
#include <osmocom/mgcp/mgcp_endp.h>
+#include <osmocom/mgcp/mgcp_sdp.h>
+#include <osmocom/mgcp/mgcp_codec.h>
#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/rate_ctr.h>
#include <ctype.h>
+static const struct rate_ctr_desc rate_ctr_desc[] = {
+ [IN_STREAM_ERR_TSTMP_CTR] = {"stream_err_tstmp:in", "Inbound rtp-stream timestamp errors."},
+ [OUT_STREAM_ERR_TSTMP_CTR] = {"stream_err_tstmp:out", "Outbound rtp-stream timestamp errors."},
+ [RTP_PACKETS_RX_CTR] = {"rtp:packets_rx", "Inbound rtp packets."},
+ [RTP_OCTETS_RX_CTR] = {"rtp:octets_rx", "Inbound rtp octets."},
+ [RTP_PACKETS_TX_CTR] = {"rtp:packets_tx", "Outbound rtp packets."},
+ [RTP_OCTETS_TX_CTR] = {"rtp:octets_rx", "Outbound rtp octets."},
+ [RTP_DROPPED_PACKETS_CTR] = {"rtp:dropped", "dropped rtp packets."}
+};
+
+const static struct rate_ctr_group_desc rate_ctr_group_desc = {
+ .group_name_prefix = "conn_rtp",
+ .group_description = "rtp connection statistics",
+ .class_id = 1,
+ .num_ctr = ARRAY_SIZE(rate_ctr_desc),
+ .ctr_desc = rate_ctr_desc
+};
+
+
/* Allocate a new connection identifier. According to RFC3435, they must
* be unique only within the scope of the endpoint. (Caller must provide
* memory for id) */
@@ -67,26 +89,14 @@ static int mgcp_alloc_id(struct mgcp_endpoint *endp, char *id)
return -1;
}
-/* Reset codec state and free memory */
-static void mgcp_rtp_codec_init(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);
-}
-
/* Initialize rtp connection struct with default values */
static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn *conn)
{
struct mgcp_rtp_end *end = &conn_rtp->end;
+ /* FIXME: Each new rate counter group requires an unique index. At the
+ * moment we generate this index using this counter, but perhaps there
+ * is a more concious way to assign the indexes. */
+ static unsigned int rate_ctr_index = 0;
conn_rtp->type = MGCP_RTP_DEFAULT;
conn_rtp->osmux.allocated_cid = -1;
@@ -96,7 +106,6 @@ static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn
end->rtp.fd = -1;
end->rtcp.fd = -1;
- memset(&end->stats, 0, sizeof(end->stats));
end->rtp_port = end->rtcp_port = 0;
talloc_free(end->fmtp_extra);
end->fmtp_extra = NULL;
@@ -105,9 +114,15 @@ static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn
end->frames_per_packet = 0; /* unknown */
end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS;
end->output_enabled = 0;
+ end->maximum_packet_time = -1;
+
+ conn_rtp->rate_ctr_group = rate_ctr_group_alloc(conn, &rate_ctr_group_desc, rate_ctr_index);
+ conn_rtp->state.in_stream.err_ts_ctr = &conn_rtp->rate_ctr_group->ctr[IN_STREAM_ERR_TSTMP_CTR];
+ conn_rtp->state.out_stream.err_ts_ctr = &conn_rtp->rate_ctr_group->ctr[OUT_STREAM_ERR_TSTMP_CTR];
+ rate_ctr_index++;
- mgcp_rtp_codec_init(&end->codec);
- mgcp_rtp_codec_init(&end->alt_codec);
+ /* Make sure codec table is reset */
+ mgcp_codec_reset_all(conn_rtp);
}
/* Cleanup rtp connection struct */
@@ -116,6 +131,7 @@ static void mgcp_rtp_conn_cleanup(struct mgcp_conn_rtp *conn_rtp)
osmux_disable_conn(conn_rtp);
osmux_release_cid(conn_rtp);
mgcp_free_rtp_port(&conn_rtp->end);
+ rate_ctr_group_free(conn_rtp->rate_ctr_group);
}
/*! allocate a new connection list entry.
diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c
index 6923b97..3ac93be 100644
--- a/src/libosmo-mgcp/mgcp_network.c
+++ b/src/libosmo-mgcp/mgcp_network.c
@@ -222,7 +222,7 @@ static int check_rtp_timestamp(struct mgcp_endpoint *endp,
if (seq == sstate->last_seq) {
if (timestamp != sstate->last_timestamp) {
- sstate->err_ts_counter += 1;
+ rate_ctr_inc(sstate->err_ts_ctr);
LOGP(DRTP, LOGL_ERROR,
"The %s timestamp delta is != 0 but the sequence "
"number %d is the same, "
@@ -272,7 +272,7 @@ static int check_rtp_timestamp(struct mgcp_endpoint *endp,
ts_alignment_error(sstate, state->packet_duration, timestamp);
if (timestamp_error) {
- sstate->err_ts_counter += 1;
+ rate_ctr_inc(sstate->err_ts_ctr);
LOGP(DRTP, LOGL_NOTICE,
"The %s timestamp has an alignment error of %d "
"on 0x%x SSRC: %u "
@@ -310,7 +310,7 @@ static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp,
ENDPOINT_NUMBER(endp), tsdelta,
inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
} else {
- tsdelta = rtp_end->codec.rate * 20 / 1000;
+ tsdelta = rtp_end->codec->rate * 20 / 1000;
LOGP(DRTP, LOGL_NOTICE,
"Fixed packet duration and last timestamp delta "
"are not available on 0x%x, "
@@ -399,12 +399,12 @@ int mgcp_rtp_processing_default(struct mgcp_endpoint *endp,
/*! 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
+ * \param[in] destination RTP connnection
+ * \param[in] source RTP connection
* \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)
+ struct mgcp_conn_rtp *conn_dst,
+ struct mgcp_conn_rtp *conn_src)
{
LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x transcoding disabled\n",
ENDPOINT_NUMBER(endp));
@@ -421,8 +421,8 @@ void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
"endpoint:0x%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;
+ *payload_type = conn->end.codec->payload_type;
+ *audio_name = conn->end.codec->audio_name;
*fmtp_extra = conn->end.fmtp_extra;
}
@@ -490,7 +490,7 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp,
uint16_t seq;
uint32_t timestamp, ssrc;
struct rtp_hdr *rtp_hdr;
- int payload = rtp_end->codec.payload_type;
+ int payload = rtp_end->codec->payload_type;
if (len < sizeof(*rtp_hdr))
return;
@@ -498,7 +498,7 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp,
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);
+ arrival_time = get_current_ts(rtp_end->codec->rate);
ssrc = ntohl(rtp_hdr->ssrc);
transit = arrival_time - timestamp;
@@ -511,7 +511,9 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp,
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_seq = seq - 1;
+ state->out_stream.ssrc = state->patch.orig_ssrc = ssrc;
+ state->out_stream.last_tsdelta = 0;
state->out_stream.last_timestamp = timestamp;
state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */
LOGP(DRTP, LOGL_INFO,
@@ -522,7 +524,7 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp,
inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
if (state->packet_duration == 0) {
state->packet_duration =
- rtp_end->codec.rate * 20 / 1000;
+ rtp_end->codec->rate * 20 / 1000;
LOGP(DRTP, LOGL_NOTICE,
"endpoint:0x%x fixed packet duration is not available, "
"using fixed 20ms instead: %d from %s:%d\n",
@@ -678,12 +680,9 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
}
LOGP(DRTP, LOGL_DEBUG,
- "endpoint:0x%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");
+ "endpoint:0x%x loop:%d, mode:%d%s\n",
+ ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode,
+ conn_src->conn->mode == MGCP_CONN_LOOPBACK ? " (loopback)" : "");
/* Note: In case of loopback configuration, both, the source and the
* destination will point to the same connection. */
@@ -692,7 +691,7 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
dest_name = conn_dst->conn->name;
if (!rtp_end->output_enabled) {
- rtp_end->stats.dropped_packets += 1;
+ rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_DROPPED_PACKETS_CTR]);
LOGP(DRTP, LOGL_DEBUG,
"endpoint:0x%x output disabled, drop to %s %s "
"rtp_port:%u rtcp_port:%u\n",
@@ -733,11 +732,18 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
* '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) {
+ if (!rtp_state->patched_first_rtp_payload
+ && conn_src->conn->mode == MGCP_CONN_LOOPBACK) {
uint8_t *data = (uint8_t *) & buf[12];
- data[0] = 0xe4;
- data[1] = 0x00;
- rtp_state->patched_first_rtp_payload = true;
+ if (data[0] == 0xe0) {
+ data[0] = 0xe4;
+ data[1] = 0x00;
+ rtp_state->patched_first_rtp_payload = true;
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:0x%x Patching over first two bytes"
+ " to fake an IuUP Initialization Ack\n",
+ ENDPOINT_NUMBER(endp));
+ }
}
len = mgcp_udp_send(rtp_end->rtp.fd,
@@ -747,8 +753,8 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
if (len <= 0)
return len;
- conn_dst->end.stats.packets_tx += 1;
- conn_dst->end.stats.octets_tx += len;
+ rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]);
+ rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len);
nbytes += len;
buflen = cont;
@@ -767,8 +773,8 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
&rtp_end->addr,
rtp_end->rtcp_port, buf, len);
- conn_dst->end.stats.packets_tx += 1;
- conn_dst->end.stats.octets_tx += len;
+ rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]);
+ rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len);
return len;
}
@@ -865,6 +871,15 @@ static int check_rtp_destin(struct mgcp_conn_rtp *conn)
struct mgcp_endpoint *endp;
endp = conn->conn->endp;
+ /* Note: it is legal to create a connection but never setting a port
+ * and IP-address for outgoing data. */
+ if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0 && conn->end.rtp_port == 0) {
+ LOGP(DRTP, LOGL_DEBUG,
+ "endpoint:0x%x destination IP-address and rtp port is (not yet) known\n",
+ ENDPOINT_NUMBER(endp));
+ return -1;
+ }
+
if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0) {
LOGP(DRTP, LOGL_ERROR,
"endpoint:0x%x destination IP-address is invalid\n",
@@ -928,8 +943,8 @@ static int mgcp_recv(int *proto, struct sockaddr_in *addr, char *buf,
}
/* Increment RX statistics */
- conn->end.stats.packets_rx += 1;
- conn->end.stats.octets_rx += rc;
+ rate_ctr_inc(&conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]);
+ rate_ctr_add(&conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR], rc);
/* Forward a copy of the RTP data to a debug ip/port */
forward_data(fd->fd, &conn->tap_in, buf, rc);
diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c
index 1d3cab3..26817c8 100644
--- a/src/libosmo-mgcp/mgcp_osmux.c
+++ b/src/libosmo-mgcp/mgcp_osmux.c
@@ -256,8 +256,8 @@ static void scheduled_tx_net_cb(struct msgb *msg, void *data)
.sin_port = conn_net->end.rtp_port,
};
- conn_bts->end.stats.octets_tx += msg->len;
- conn_bts->end.stats.packets_tx++;
+ rate_ctr_inc(&conn_bts->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]);
+ rate_ctr_add(&conn_bts->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], msg->len);
/* Send RTP data to NET */
/* FIXME: Get rid of conn_bts and conn_net! */
@@ -283,8 +283,8 @@ static void scheduled_tx_bts_cb(struct msgb *msg, void *data)
.sin_port = conn_bts->end.rtp_port,
};
- conn_net->end.stats.octets_tx += msg->len;
- conn_net->end.stats.packets_tx++;
+ rate_ctr_inc(&conn_net->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]);
+ rate_ctr_add(&conn_net->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], msg->len);
/* Send RTP data to BTS */
/* FIXME: Get rid of conn_bts and conn_net! */
@@ -322,11 +322,10 @@ 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;
+ struct mgcp_conn_rtp *conn_bts = NULL;
msg = osmux_recv(ofd, &addr);
if (!msg)
@@ -345,8 +344,8 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what)
&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)
+ conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS);
+ if (!conn_bts)
goto out;
if (!endp) {
@@ -355,12 +354,11 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what)
osmuxh->circuit_id);
goto out;
}
- conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem);
- conn_net->osmux.stats.chunks++;
+ conn_bts->osmux.stats.octets += osmux_chunk_length(msg, rem);
+ conn_bts->osmux.stats.chunks++;
rem = msg->len;
- osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list);
- osmux_tx_sched(&list, scheduled_tx_bts_cb, endp);
+ osmux_xfrm_output_sched(&conn_bts->osmux.out, osmuxh);
}
out:
msgb_free(msg);
@@ -426,7 +424,6 @@ 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;
@@ -463,8 +460,7 @@ int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what)
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);
+ osmux_xfrm_output_sched(&conn_net->osmux.out, osmuxh);
}
out:
msgb_free(msg);
@@ -553,9 +549,13 @@ int osmux_enable_conn(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn,
switch (endp->cfg->role) {
case MGCP_BSC_NAT:
conn->type = MGCP_OSMUX_BSC_NAT;
+ osmux_xfrm_output_set_tx_cb(&conn->osmux.out,
+ scheduled_tx_net_cb, endp);
break;
case MGCP_BSC:
conn->type = MGCP_OSMUX_BSC;
+ osmux_xfrm_output_set_tx_cb(&conn->osmux.out,
+ scheduled_tx_bts_cb, endp);
break;
}
@@ -576,6 +576,11 @@ void osmux_disable_conn(struct mgcp_conn_rtp *conn)
LOGP(DLMGCP, LOGL_INFO, "Releasing connection %s using Osmux CID %u\n",
conn->conn->id, conn->osmux.cid);
+
+ /* We are closing, we don't need pending RTP packets to be transmitted */
+ osmux_xfrm_output_set_tx_cb(&conn->osmux.out, NULL, NULL);
+ osmux_xfrm_output_flush(&conn->osmux.out);
+
osmux_xfrm_input_close_circuit(conn->osmux.in, conn->osmux.cid);
conn->osmux.state = OSMUX_STATE_DISABLED;
conn->osmux.cid = -1;
diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c
index ea86672..ea80907 100644
--- a/src/libosmo-mgcp/mgcp_protocol.c
+++ b/src/libosmo-mgcp/mgcp_protocol.c
@@ -40,6 +40,7 @@
#include <osmocom/mgcp/mgcp_msg.h>
#include <osmocom/mgcp/mgcp_endp.h>
#include <osmocom/mgcp/mgcp_sdp.h>
+#include <osmocom/mgcp/mgcp_codec.h>
struct mgcp_request {
char *name;
@@ -88,8 +89,7 @@ static int setup_rtp_processing(struct mgcp_endpoint *endp,
}
}
- return cfg->setup_rtp_processing_cb(endp, &conn_dst->end,
- &conn_src->end);
+ return cfg->setup_rtp_processing_cb(endp, conn_dst, conn_src);
}
/* array of function pointers for handling various
@@ -356,13 +356,15 @@ static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p)
/* Try to find a free port by attempting 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 attempt,
- * nevertheless, try at least the next 200 ports before giving up */
+ * to the next free port, binding should in work on the first attempt in
+ * general. In case of failure the next port is tryed until the whole port
+ * range is tryed once. */
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;
+ unsigned int tries;
OSMO_ASSERT(conn);
end = &conn->end;
@@ -371,7 +373,8 @@ static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn)
range = &endp->cfg->net_ports;
/* attempt to find a port */
- for (i = 0; i < 200; ++i) {
+ tries = (range->range_end - range->range_start) / 2;
+ for (i = 0; i < tries; ++i) {
int rc;
if (range->last_port >= range->range_end)
@@ -387,8 +390,123 @@ static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn)
}
LOGP(DLMGCP, LOGL_ERROR,
- "Allocating a RTP/RTCP port failed 200 times 0x%x.\n",
- ENDPOINT_NUMBER(endp));
+ "Allocating a RTP/RTCP port failed %u times 0x%x.\n",
+ tries, ENDPOINT_NUMBER(endp));
+ return -1;
+}
+
+/*! Helper function for check_local_cx_options() to get a pointer of the next
+ * lco option identifier
+ * \param[in] lco string
+ * \returns pointer to the beginning of the LCO identifier, NULL on failure */
+char *get_lco_identifier(const char *options)
+{
+ char *ptr;
+ unsigned int count = 0;
+
+ /* Jump to the end of the lco identifier */
+ ptr = strstr(options, ":");
+ if (!ptr)
+ return NULL;
+
+ /* Walk backwards until the pointer points to the beginning of the
+ * lco identifier. We know that we stand at the beginning when we
+ * are either at the beginning of the memory or see a space or
+ * comma. (this is tolerant, it will accept a:10, b:11 as well as
+ * a:10,b:11) */
+ while (1) {
+ /* Endless loop protection */
+ if (count > 10000)
+ return NULL;
+ else if (ptr < options || *ptr == ' ' || *ptr == ',') {
+ ptr++;
+ break;
+ }
+ ptr--;
+ count++;
+ }
+
+ /* Check if we got any result */
+ if (*ptr == ':')
+ return NULL;
+
+ return ptr;
+}
+
+/*! Check the LCO option. This function checks for multiple appearence of LCO
+ * options, which is illegal
+ * \param[in] ctx talloc context
+ * \param[in] lco string
+ * \returns 0 on success, -1 on failure */
+int check_local_cx_options(void *ctx, const char *options)
+{
+ int i;
+ char *options_copy;
+ char *lco_identifier;
+ char *lco_identifier_end;
+ char *next_lco_identifier;
+
+ char **lco_seen;
+ unsigned int lco_seen_n = 0;
+
+ if (!options)
+ return -1;
+
+ lco_seen =
+ (char **)talloc_zero_size(ctx, strlen(options) * sizeof(char *));
+ options_copy = talloc_strdup(ctx, options);
+ lco_identifier = options_copy;
+
+ do {
+ /* Move the lco_identifier pointer to the beginning of the
+ * current lco option identifier */
+ lco_identifier = get_lco_identifier(lco_identifier);
+ if (!lco_identifier)
+ goto error;
+
+ /* Look ahead to the next LCO option early, since we
+ * will parse destructively */
+ next_lco_identifier = strstr(lco_identifier + 1, ",");
+
+ /* Pinch off the end of the lco field identifier name
+ * and see if we still got something, also check if
+ * there is some value after the colon. */
+ lco_identifier_end = strstr(lco_identifier, ":");
+ if (!lco_identifier_end)
+ goto error;
+ if (*(lco_identifier_end + 1) == ' '
+ || *(lco_identifier_end + 1) == ','
+ || *(lco_identifier_end + 1) == '\0')
+ goto error;
+ *lco_identifier_end = '\0';
+ if (strlen(lco_identifier) == 0)
+ goto error;
+
+ /* Check if we have already seen the current field identifier
+ * before. If yes, we must bail, an LCO must only appear once
+ * in the LCO string */
+ for (i = 0; i < lco_seen_n; i++) {
+ if (strcmp(lco_seen[i], lco_identifier) == 0)
+ goto error;
+ }
+ lco_seen[lco_seen_n] = lco_identifier;
+ lco_seen_n++;
+
+ /* The first identifier must always be found at the beginnning
+ * of the LCO string */
+ if (lco_seen[0] != options_copy)
+ goto error;
+
+ /* Go to the next lco option */
+ lco_identifier = next_lco_identifier;
+ } while (lco_identifier);
+
+ talloc_free(lco_seen);
+ talloc_free(options_copy);
+ return 0;
+error:
+ talloc_free(lco_seen);
+ talloc_free(options_copy);
return -1;
}
@@ -402,20 +520,34 @@ static int set_local_cx_options(void *ctx, struct mgcp_lco *lco,
char *p_opt, *a_opt;
char codec[9];
+ if (!options)
+ return 0;
+ if (strlen(options) == 0)
+ return 0;
+
+ /* Make sure the encoding of the LCO is consistant before we proceed */
+ if (check_local_cx_options(ctx, options) != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "local CX options: Internal inconsistency in Local Connection Options!\n");
+ return 524;
+ }
+
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 : "");
+ lco->string = talloc_strdup(ctx, 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;
+ /* FIXME: LCO also supports the negotiation of more then one codec.
+ * (e.g. a:PCMU;G726-32) But this implementation only supports a single
+ * codec only. */
a_opt = strstr(lco->string, "a:");
- if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1)
+ if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) {
+ talloc_free(lco->codec);
lco->codec = talloc_strdup(ctx, codec);
+ }
LOGP(DLMGCP, LOGL_DEBUG,
"local CX options: lco->pkt_period_max: %i, lco->codec: %s\n",
@@ -456,15 +588,15 @@ uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp,
/* 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 +
+ 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;
+ 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)
@@ -480,6 +612,68 @@ static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line)
return mgcp_parse_osmux_cid(line);
}
+/* Process codec information contained in CRCX/MDCX */
+static int handle_codec_info(struct mgcp_conn_rtp *conn,
+ struct mgcp_parse_data *p, int have_sdp, bool crcx)
+{
+ struct mgcp_endpoint *endp = p->endp;
+ int rc;
+ char *cmd;
+
+ if (crcx)
+ cmd = "CRCX";
+ else
+ cmd = "MDCX";
+
+ /* Collect codec information */
+ if (have_sdp) {
+ /* If we have SDP, we ignore the local connection options and
+ * use only the SDP information. */
+ mgcp_codec_reset_all(conn);
+ rc = mgcp_parse_sdp_data(endp, conn, p);
+ if (rc != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "%s: endpoint:%x sdp not parseable\n", cmd,
+ ENDPOINT_NUMBER(endp));
+
+ /* See also RFC 3661: Protocol error */
+ return 510;
+ }
+ } else if (endp->local_options.codec) {
+ /* When no SDP is available, we use the codec information from
+ * the local connection options (if present) */
+ mgcp_codec_reset_all(conn);
+ rc = mgcp_codec_add(conn, PTYPE_UNDEFINED, endp->local_options.codec);
+ if (rc != 0)
+ goto error;
+ }
+
+ /* Make sure we always set a sane default codec */
+ if (conn->end.codecs_assigned == 0) {
+ /* When SDP and/or LCO did not supply any codec information,
+ * than it makes sense to pick a sane default: (payload-type 0,
+ * PCMU), see also: OS#2658 */
+ mgcp_codec_reset_all(conn);
+ rc = mgcp_codec_add(conn, 0, NULL);
+ if (rc != 0)
+ goto error;
+ }
+
+ /* Make codec decision */
+ if (mgcp_codec_decide(conn) != 0)
+ goto error;
+
+ return 0;
+
+error:
+ LOGP(DLMGCP, LOGL_ERROR,
+ "%s: endpoint:0x%x codec negotiation failure\n", cmd,
+ ENDPOINT_NUMBER(endp));
+
+ /* See also RFC 3661: Codec negotiation failure */
+ return 534;
+}
+
/* CRCX command handler, processes the received command */
static struct msgb *handle_create_con(struct mgcp_parse_data *p)
{
@@ -597,17 +791,6 @@ mgcp_header_done:
* connection ids) */
endp->callid = talloc_strdup(tcfg->endpoints, callid);
- /* Extract audio codec information */
- rc = set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
- local_options);
- if (rc != 0) {
- LOGP(DLMGCP, LOGL_ERROR,
- "CRCX: endpoint:%x inavlid local connection options!\n",
- ENDPOINT_NUMBER(endp));
- error_code = rc;
- goto error2;
- }
-
snprintf(conn_name, sizeof(conn_name), "%s", callid);
_conn = mgcp_conn_alloc(NULL, endp, MGCP_CONN_TYPE_RTP, conn_name);
if (!_conn) {
@@ -638,12 +821,27 @@ mgcp_header_done:
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);
+ /* Set local connection options, if present */
+ if (local_options) {
+ rc = set_local_cx_options(endp->tcfg->endpoints,
+ &endp->local_options, local_options);
+ if (rc != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "CRCX: endpoint:%x inavlid local connection options!\n",
+ ENDPOINT_NUMBER(endp));
+ error_code = rc;
+ goto error2;
+ }
+ }
+
+ /* Handle codec information and decide for a suitable codec */
+ rc = handle_codec_info(conn, p, have_sdp, true);
+ mgcp_codec_summary(conn);
+ if (rc) {
+ error_code = rc;
+ goto error2;
+ }
+
conn->end.fmtp_extra = talloc_strdup(tcfg->endpoints,
tcfg->audio_fmtp_extra);
@@ -718,11 +916,15 @@ mgcp_header_done:
error2:
mgcp_endp_release(endp);
LOGP(DLMGCP, LOGL_NOTICE,
- "CRCX: endpoint:0x%x unable to create connection resource error\n",
+ "CRCX: endpoint:0x%x unable to create connection\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)
{
@@ -814,23 +1016,27 @@ mgcp_header_done:
} else
conn->conn->mode = conn->conn->mode_orig;
- if (have_sdp)
- mgcp_parse_sdp_data(endp, conn, p);
+ /* Set local connection options, if present */
+ if (local_options) {
+ rc = set_local_cx_options(endp->tcfg->endpoints,
+ &endp->local_options, local_options);
+ if (rc != 0) {
+ LOGP(DLMGCP, LOGL_ERROR,
+ "MDCX: endpoint:%x inavlid local connection options!\n",
+ ENDPOINT_NUMBER(endp));
+ error_code = rc;
+ goto error3;
+ }
+ }
- rc = set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
- local_options);
- if (rc != 0) {
- LOGP(DLMGCP, LOGL_ERROR,
- "MDCX: endpoint:%x inavlid local connection options!\n",
- ENDPOINT_NUMBER(endp));
+ /* Handle codec information and decide for a suitable codec */
+ rc = handle_codec_info(conn, p, have_sdp, false);
+ mgcp_codec_summary(conn);
+ if (rc) {
error_code = rc;
goto error3;
}
- if (!have_sdp && endp->local_options.codec)
- mgcp_set_audio_info(p->cfg, &conn->end.codec,
- PTYPE_UNDEFINED, endp->local_options.codec);
-
/* check connection mode setting */
if (conn->conn->mode != MGCP_CONN_LOOPBACK
&& conn->conn->mode != MGCP_CONN_RECV_ONLY
@@ -842,6 +1048,7 @@ mgcp_header_done:
goto error3;
}
+
if (setup_rtp_processing(endp, conn) != 0)
goto error3;
diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c
index 52b4df4..102c8c3 100644
--- a/src/libosmo-mgcp/mgcp_sdp.c
+++ b/src/libosmo-mgcp/mgcp_sdp.c
@@ -25,9 +25,13 @@
#include <osmocom/mgcp/mgcp_internal.h>
#include <osmocom/mgcp/mgcp_msg.h>
#include <osmocom/mgcp/mgcp_endp.h>
+#include <osmocom/mgcp/mgcp_codec.h>
#include <errno.h>
+/* A struct to store intermediate parsing results. The function
+ * mgcp_parse_sdp_data() is using it as temporary storage for parsing the SDP
+ * codec information. */
struct sdp_rtp_map {
/* the type */
int payload_type;
@@ -40,89 +44,8 @@ struct sdp_rtp_map {
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;
-}
-
+/* Helper function to extrapolate missing codec parameters in a codec mao from
+ * an already filled in payload_type, called from: mgcp_parse_sdp_data() */
static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used)
{
int i;
@@ -149,10 +72,16 @@ static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used)
codecs[i].rate = 8000;
codecs[i].channels = 1;
break;
+ default:
+ codecs[i].codec_name = NULL;
+ codecs[i].rate = 0;
+ codecs[i].channels = 0;
}
}
}
+/* Helper function to update codec map information with additional data from
+ * SDP, called from: mgcp_parse_sdp_data() */
static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used,
int payload, const char *audio_name)
{
@@ -162,8 +91,13 @@ static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used,
char audio_codec[64];
int rate = -1;
int channels = -1;
+
+ /* Note: We can only update payload codecs that already exist
+ * in our codec list. If we get an unexpected payload type,
+ * we just drop it */
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",
@@ -182,43 +116,72 @@ static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used,
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)
+/* Extract payload types from SDP, also check for duplicates */
+static int pt_from_sdp(void *ctx, struct sdp_rtp_map *codecs,
+ unsigned int codecs_len, char *sdp)
{
- char *codec_str;
- char audio_codec[64];
+ char *str;
+ char *str_ptr;
+ char *pt_str;
+ unsigned int pt;
+ unsigned int count = 0;
+ unsigned int i;
+
+ str = talloc_zero_size(ctx, strlen(sdp) + 1);
+ str_ptr = str;
+ strcpy(str_ptr, sdp);
+
+ str_ptr = strstr(str_ptr, "RTP/AVP ");
+ if (!str_ptr)
+ goto exit;
+
+ pt_str = strtok(str_ptr, " ");
+ if (!pt_str)
+ goto exit;
+
+ while (1) {
+ /* Do not allow excessive payload types */
+ if (count > codecs_len)
+ goto error;
+
+ pt_str = strtok(NULL, " ");
+ if (!pt_str)
+ break;
- if (!codec->codec_name)
- return 0;
+ pt = atoi(pt_str);
- /* 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;
+ /* Do not allow duplicate payload types */
+ for (i = 0; i < count; i++)
+ if (codecs[i].payload_type == pt)
+ goto error;
+
+ codecs[count].payload_type = pt;
+ count++;
+ }
- return strcasecmp(audio_codec, codec->codec_name) == 0;
+exit:
+ talloc_free(str);
+ return count;
+error:
+ talloc_free(str);
+ return -EINVAL;
}
/*! 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 */
+ * rtp port, rtcp port and the codec information.
+ * \returns 0 on success, -1 on failure. */
int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp,
- struct mgcp_conn_rtp *conn,
- struct mgcp_parse_data *p)
+ struct mgcp_conn_rtp *conn, struct mgcp_parse_data *p)
{
- struct sdp_rtp_map codecs[10];
- int codecs_used = 0;
+ struct sdp_rtp_map codecs[MGCP_MAX_CODECS];
+ unsigned int codecs_used = 0;
char *line;
- int maxptime = -1;
- int i;
- int codecs_assigned = 0;
+ unsigned int i;
void *tmp_ctx = talloc_new(NULL);
struct mgcp_rtp_end *rtp;
@@ -255,30 +218,21 @@ int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp,
rtp->packet_duration_ms = 0;
else
rtp->packet_duration_ms = ptime;
- } else if (sscanf(line, "a=maxptime:%d", &ptime2)
- == 1) {
- maxptime = ptime2;
+ } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) {
+ rtp->maximum_packet_time = 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) {
+ rc = sscanf(line, "m=audio %d RTP/AVP", &port);
+ if (rc == 1) {
rtp->rtp_port = htons(port);
rtp->rtcp_port = htons(port + 1);
- codecs_used = rc - 1;
- codecs_initialize(tmp_ctx, codecs, codecs_used);
}
+
+ rc = pt_from_sdp(conn->conn, codecs,
+ ARRAY_SIZE(codecs), line);
+ if (rc > 0)
+ codecs_used = rc;
break;
case 'c':
@@ -299,43 +253,37 @@ int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp,
break;
}
}
+ OSMO_ASSERT(codecs_used <= MGCP_MAX_CODECS);
- /* 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;
- }
+ /* So far we have only set the payload type in the codec struct. Now we
+ * fill up the remaining fields of the codec description with some default
+ * information */
+ codecs_initialize(tmp_ctx, codecs, codecs_used);
- mgcp_set_audio_info(p->cfg, codec,
- codecs[i].payload_type, codecs[i].map_line);
- codecs_assigned += 1;
+ /* Store parsed codec information */
+ for (i = 0; i < codecs_used; i++) {
+ rc = mgcp_codec_add(conn, codecs[i].payload_type, codecs[i].map_line);
+ if (rc < 0)
+ LOGP(DLMGCP, LOGL_NOTICE, "endpoint:0x%x, failed to add codec\n", ENDPOINT_NUMBER(p->endp));
}
- 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;
- }
+ talloc_free(tmp_ctx);
- 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));
+ LOGP(DLMGCP, LOGL_NOTICE,
+ "Got media info via SDP: port:%d, addr:%s, duration:%d, payload-types:",
+ ntohs(rtp->rtp_port), inet_ntoa(rtp->addr),
+ rtp->packet_duration_ms);
+ if (codecs_used == 0)
+ LOGPC(DLMGCP, LOGL_NOTICE, "none");
+ for (i = 0; i < codecs_used; i++) {
+ LOGPC(DLMGCP, LOGL_NOTICE, "%d=%s",
+ rtp->codecs[i].payload_type,
+ rtp->codecs[i].subtype_name ? rtp-> codecs[i].subtype_name : "unknown");
+ LOGPC(DLMGCP, LOGL_NOTICE, " ");
}
+ LOGPC(DLMGCP, LOGL_NOTICE, "\n");
- talloc_free(tmp_ctx);
- return codecs_assigned > 0;
+ return 0;
}
/*! Generate SDP response string.
@@ -380,7 +328,9 @@ int mgcp_write_response_sdp(const struct mgcp_endpoint *endp,
if (rc < 0)
goto buffer_too_small;
- if (audio_name && endp->tcfg->audio_send_name) {
+ /* FIXME: Check if the payload type is from the static range,
+ * if yes, omitthe a=rtpmap since it is unnecessary */
+ if (audio_name && endp->tcfg->audio_send_name && (payload_type >= 96 && payload_type <= 127)) {
rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n",
payload_type, audio_name);
diff --git a/src/libosmo-mgcp/mgcp_stat.c b/src/libosmo-mgcp/mgcp_stat.c
index 581130c..3685cfe 100644
--- a/src/libosmo-mgcp/mgcp_stat.c
+++ b/src/libosmo-mgcp/mgcp_stat.c
@@ -25,12 +25,14 @@
#include <osmocom/mgcp/mgcp_stat.h>
#include <osmocom/mgcp/mgcp_endp.h>
#include <limits.h>
+#include <inttypes.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)
+void calc_loss(struct mgcp_conn_rtp *conn, uint32_t *expected, int *loss)
{
+ struct mgcp_rtp_state *state = &conn->state;
+ struct rate_ctr *packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR];
+
*expected = state->stats.cycles + state->stats.max_seq;
*expected = *expected - state->stats.base_seq + 1;
@@ -44,8 +46,8 @@ void calc_loss(struct mgcp_rtp_state *state,
* Make sure the sign is correct and use the biggest
* positive/negative number that fits.
*/
- *loss = *expected - end->stats.packets_rx;
- if (*expected < end->stats.packets_rx) {
+ *loss = *expected - packets_rx->current;
+ if (*expected < packets_rx->current) {
if (*loss > 0)
*loss = INT_MIN;
} else {
@@ -70,13 +72,18 @@ static void mgcp_format_stats_rtp(char *str, size_t str_len,
int ploss;
int nchars;
- calc_loss(&conn->state, &conn->end, &expected, &ploss);
+ struct rate_ctr *packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR];
+ struct rate_ctr *octets_rx = &conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR];
+ struct rate_ctr *packets_tx = &conn->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR];
+ struct rate_ctr *octets_tx = &conn->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR];
+
+ calc_loss(conn, &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.stats.packets_tx, conn->end.stats.octets_tx,
- conn->end.stats.packets_rx, conn->end.stats.octets_rx,
+ "\r\nP: PS=%" PRIu64 ", OS=%" PRIu64 ", PR=%" PRIu64 ", OR=%" PRIu64 ", PL=%d, JI=%u",
+ packets_tx->current, octets_tx->current,
+ packets_rx->current, octets_rx->current,
ploss, jitter);
if (nchars < 0 || nchars >= str_len)
goto truncate;
@@ -87,9 +94,9 @@ static void mgcp_format_stats_rtp(char *str, size_t str_len,
if (conn->conn->endp->cfg->osmux != OSMUX_USAGE_OFF) {
/* 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);
+ "\r\nX-Osmo-CP: EC TI=%" PRIu64 ", TO=%" PRIu64,
+ conn->state.in_stream.err_ts_ctr->current,
+ conn->state.out_stream.err_ts_ctr->current);
if (nchars < 0 || nchars >= str_len)
goto truncate;
diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c
index 14ecd17..ad462b7 100644
--- a/src/libosmo-mgcp/mgcp_vty.c
+++ b/src/libosmo-mgcp/mgcp_vty.c
@@ -30,6 +30,7 @@
#include <osmocom/mgcp/mgcp_endp.h>
#include <string.h>
+#include <inttypes.h>
#define RTCP_OMIT_STR "Drop RTCP packets in both directions\n"
#define RTP_PATCH_STR "Modify RTP packet header in both directions\n"
@@ -154,22 +155,27 @@ static int config_write_mgcp(struct vty *vty)
return CMD_SUCCESS;
}
-static void dump_rtp_end(struct vty *vty, struct mgcp_rtp_state *state,
- struct mgcp_rtp_end *end)
+static void dump_rtp_end(struct vty *vty, struct mgcp_conn_rtp *conn)
{
- struct mgcp_rtp_codec *codec = &end->codec;
+ struct mgcp_rtp_state *state = &conn->state;
+ struct mgcp_rtp_end *end = &conn->end;
+ struct mgcp_rtp_codec *codec = end->codec;
+ struct rate_ctr *dropped_packets;
+
+ dropped_packets = &conn->rate_ctr_group->ctr[RTP_DROPPED_PACKETS_CTR];
vty_out(vty,
- " Timestamp Errs: %d->%d%s"
- " Dropped Packets: %d%s"
+ " Timestamp Errs: %" PRIu64 "->%" PRIu64 "%s"
+ " Dropped Packets: %" PRIu64 "%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->stats.dropped_packets, VTY_NEWLINE,
+ state->in_stream.err_ts_ctr->current,
+ state->out_stream.err_ts_ctr->current,
+ VTY_NEWLINE,
+ dropped_packets->current, 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,
@@ -207,8 +213,7 @@ static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg,
* 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);
+ dump_rtp_end(vty, &conn->u.rtp);
}
}
}
@@ -281,13 +286,6 @@ DEFUN(cfg_mgcp_bind_early,
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"
@@ -296,11 +294,38 @@ static void parse_range(struct mgcp_port_range *range, const char **argv)
DEFUN(cfg_mgcp_rtp_port_range,
cfg_mgcp_rtp_port_range_cmd,
- "rtp port-range <0-65534> <0-65534>",
+ "rtp port-range <1024-65534> <1025-65535>",
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);
+ int start;
+ int end;
+
+ start = atoi(argv[0]);
+ end = atoi(argv[1]);
+
+ if (end < start) {
+ vty_out(vty, "range end port (%i) must be greater than the range start port (%i)!%s",
+ end, start, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (start & 1) {
+ vty_out(vty, "range must begin at an even port number, autocorrecting port (%i) to: %i%s",
+ start, start & 0xFFFE, VTY_NEWLINE);
+ start &= 0xFFFE;
+ }
+
+ if ((end & 1) == 0) {
+ vty_out(vty, "range must end at an odd port number, autocorrecting port (%i) to: %i%s",
+ end, end | 1, VTY_NEWLINE);
+ end |= 1;
+ }
+
+ g_cfg->net_ports.range_start = start;
+ g_cfg->net_ports.range_end = end;
+ g_cfg->net_ports.last_port = g_cfg->net_ports.range_start;
+
return CMD_SUCCESS;
}
ALIAS_DEPRECATED(cfg_mgcp_rtp_port_range,