From d45a0642eed2a5c42fe5c9387d66808bc2f78b0b Mon Sep 17 00:00:00 2001 From: Ruben Undheim Date: Tue, 13 Nov 2018 08:11:52 +0100 Subject: New upstream version 1.4.0 --- src/libosmo-mgcp/Makefile.am | 1 + src/libosmo-mgcp/mgcp_codec.c | 343 +++++++++++++++++++++++++++++++++++++++ src/libosmo-mgcp/mgcp_conn.c | 54 +++--- src/libosmo-mgcp/mgcp_network.c | 75 +++++---- src/libosmo-mgcp/mgcp_osmux.c | 35 ++-- src/libosmo-mgcp/mgcp_protocol.c | 301 ++++++++++++++++++++++++++++------ src/libosmo-mgcp/mgcp_sdp.c | 258 ++++++++++++----------------- src/libosmo-mgcp/mgcp_stat.c | 31 ++-- src/libosmo-mgcp/mgcp_vty.c | 63 ++++--- 9 files changed, 865 insertions(+), 296 deletions(-) create mode 100644 src/libosmo-mgcp/mgcp_codec.c (limited to 'src/libosmo-mgcp') 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 + * (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 . + * + */ +#include +#include +#include + +/* 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 #include #include +#include +#include #include +#include #include +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 #include #include +#include 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 #include #include +#include #include +/* 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 #include #include +#include /* 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 #include +#include #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, -- cgit v1.2.3