mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-mgw.git
synced 2025-10-23 08:12:01 +00:00
The codec negotiation via SDP is currently in a neglected state. Also osmo-mgw does some kind of codec decision wile the SDP is parsed, the result is information for one codec, even when there are multiple codecs negotiated. This is problematic because we loose all information about alternate codecs while we parse. This should be untangled and the information should be presevered. Also we are not really capable picking a default. Wehen we do not supply any codec information (not even LCO), then we should pick a sane default codec. - separate the codec decision from the sdp parser and concentrate codec related code in a separate c file - add support for multiple codecs in one SDP negotiation - do not initalize "magic" codec defaults during conn allocation - do not allow invalid payload types, especially not 255. When someone tries to select an invalid payload type, do not fail hard, just pick a sane default. - handle the codec decision in protocol.c, pick a sane default codec when no (valid) codec has been negotiated (no LCO, no SDP) Change-Id: If730d022ba6bdb217ad4e20b3fbbd1114dbb4b8f Closes: OS#2658 Related: OS#3114 Related: OS#2728
361 lines
9.2 KiB
C
361 lines
9.2 KiB
C
/*
|
|
* Some SDP file parsing...
|
|
*
|
|
* (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
|
|
* (C) 2009-2014 by On-Waves
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <osmocom/core/msgb.h>
|
|
#include <osmocom/mgcp/mgcp.h>
|
|
#include <osmocom/mgcp/mgcp_internal.h>
|
|
#include <osmocom/mgcp/mgcp_msg.h>
|
|
#include <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;
|
|
/* null, static or later dynamic codec name */
|
|
char *codec_name;
|
|
/* A pointer to the original line for later parsing */
|
|
char *map_line;
|
|
|
|
int rate;
|
|
int channels;
|
|
};
|
|
|
|
/* 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;
|
|
|
|
for (i = 0; i < used; ++i) {
|
|
switch (codecs[i].payload_type) {
|
|
case 0:
|
|
codecs[i].codec_name = "PCMU";
|
|
codecs[i].rate = 8000;
|
|
codecs[i].channels = 1;
|
|
break;
|
|
case 3:
|
|
codecs[i].codec_name = "GSM";
|
|
codecs[i].rate = 8000;
|
|
codecs[i].channels = 1;
|
|
break;
|
|
case 8:
|
|
codecs[i].codec_name = "PCMA";
|
|
codecs[i].rate = 8000;
|
|
codecs[i].channels = 1;
|
|
break;
|
|
case 18:
|
|
codecs[i].codec_name = "G729";
|
|
codecs[i].rate = 8000;
|
|
codecs[i].channels = 1;
|
|
break;
|
|
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)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < used; ++i) {
|
|
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",
|
|
audio_name);
|
|
continue;
|
|
}
|
|
|
|
codecs[i].map_line = talloc_strdup(ctx, audio_name);
|
|
codecs[i].codec_name = talloc_strdup(ctx, audio_codec);
|
|
codecs[i].rate = rate;
|
|
codecs[i].channels = channels;
|
|
return;
|
|
}
|
|
|
|
LOGP(DLMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload,
|
|
audio_name);
|
|
}
|
|
|
|
/* 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 *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;
|
|
|
|
pt = atoi(pt_str);
|
|
|
|
/* 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++;
|
|
}
|
|
|
|
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.
|
|
*
|
|
* Note: In conn (conn->end) the function returns the packet duration,
|
|
* 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 sdp_rtp_map codecs[MGCP_MAX_CODECS];
|
|
unsigned int codecs_used = 0;
|
|
char *line;
|
|
unsigned int i;
|
|
void *tmp_ctx = talloc_new(NULL);
|
|
struct mgcp_rtp_end *rtp;
|
|
|
|
int payload;
|
|
int ptime, ptime2 = 0;
|
|
char audio_name[64];
|
|
int port, rc;
|
|
char ipv4[16];
|
|
|
|
OSMO_ASSERT(endp);
|
|
OSMO_ASSERT(conn);
|
|
OSMO_ASSERT(p);
|
|
|
|
rtp = &conn->end;
|
|
memset(&codecs, 0, sizeof(codecs));
|
|
|
|
for_each_line(line, p->save) {
|
|
switch (line[0]) {
|
|
case 'o':
|
|
case 's':
|
|
case 't':
|
|
case 'v':
|
|
/* skip these SDP attributes */
|
|
break;
|
|
case 'a':
|
|
if (sscanf(line, "a=rtpmap:%d %63s",
|
|
&payload, audio_name) == 2) {
|
|
codecs_update(tmp_ctx, codecs,
|
|
codecs_used, payload, audio_name);
|
|
} else
|
|
if (sscanf
|
|
(line, "a=ptime:%d-%d", &ptime, &ptime2) >= 1) {
|
|
if (ptime2 > 0 && ptime2 != ptime)
|
|
rtp->packet_duration_ms = 0;
|
|
else
|
|
rtp->packet_duration_ms = ptime;
|
|
} else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) {
|
|
rtp->maximum_packet_time = ptime2;
|
|
}
|
|
break;
|
|
case 'm':
|
|
rc = sscanf(line, "m=audio %d RTP/AVP", &port);
|
|
if (rc == 1) {
|
|
rtp->rtp_port = htons(port);
|
|
rtp->rtcp_port = htons(port + 1);
|
|
}
|
|
|
|
rc = pt_from_sdp(conn->conn, codecs,
|
|
ARRAY_SIZE(codecs), line);
|
|
if (rc > 0)
|
|
codecs_used = rc;
|
|
break;
|
|
case 'c':
|
|
|
|
if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) {
|
|
inet_aton(ipv4, &rtp->addr);
|
|
}
|
|
break;
|
|
default:
|
|
if (p->endp)
|
|
LOGP(DLMGCP, LOGL_NOTICE,
|
|
"Unhandled SDP option: '%c'/%d on 0x%x\n",
|
|
line[0], line[0],
|
|
ENDPOINT_NUMBER(p->endp));
|
|
else
|
|
LOGP(DLMGCP, LOGL_NOTICE,
|
|
"Unhandled SDP option: '%c'/%d\n",
|
|
line[0], line[0]);
|
|
break;
|
|
}
|
|
}
|
|
OSMO_ASSERT(codecs_used <= MGCP_MAX_CODECS);
|
|
|
|
/* 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);
|
|
|
|
/* 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));
|
|
}
|
|
|
|
talloc_free(tmp_ctx);
|
|
|
|
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");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*! Generate SDP response string.
|
|
* \param[in] endp trunk endpoint.
|
|
* \param[in] conn associated rtp connection.
|
|
* \param[out] sdp msg buffer to append resulting SDP string data.
|
|
* \param[in] addr IPV4 address string (e.g. 192.168.100.1).
|
|
* \returns 0 on success, -1 on failure. */
|
|
int mgcp_write_response_sdp(const struct mgcp_endpoint *endp,
|
|
const struct mgcp_conn_rtp *conn, struct msgb *sdp,
|
|
const char *addr)
|
|
{
|
|
const char *fmtp_extra;
|
|
const char *audio_name;
|
|
int payload_type;
|
|
int rc;
|
|
|
|
OSMO_ASSERT(endp);
|
|
OSMO_ASSERT(conn);
|
|
OSMO_ASSERT(sdp);
|
|
OSMO_ASSERT(addr);
|
|
|
|
/* FIXME: constify endp and conn args in get_net_donwlink_format_cb() */
|
|
endp->cfg->get_net_downlink_format_cb((struct mgcp_endpoint *)endp,
|
|
&payload_type, &audio_name,
|
|
&fmtp_extra,
|
|
(struct mgcp_conn_rtp *)conn);
|
|
|
|
rc = msgb_printf(sdp,
|
|
"v=0\r\n"
|
|
"o=- %s 23 IN IP4 %s\r\n"
|
|
"s=-\r\n"
|
|
"c=IN IP4 %s\r\n"
|
|
"t=0 0\r\n", conn->conn->id, addr, addr);
|
|
|
|
if (rc < 0)
|
|
goto buffer_too_small;
|
|
|
|
if (payload_type >= 0) {
|
|
rc = msgb_printf(sdp, "m=audio %d RTP/AVP %d\r\n",
|
|
conn->end.local_port, payload_type);
|
|
if (rc < 0)
|
|
goto buffer_too_small;
|
|
|
|
/* 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);
|
|
|
|
if (rc < 0)
|
|
goto buffer_too_small;
|
|
}
|
|
|
|
if (fmtp_extra) {
|
|
rc = msgb_printf(sdp, "%s\r\n", fmtp_extra);
|
|
|
|
if (rc < 0)
|
|
goto buffer_too_small;
|
|
}
|
|
}
|
|
if (conn->end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) {
|
|
rc = msgb_printf(sdp, "a=ptime:%u\r\n",
|
|
conn->end.packet_duration_ms);
|
|
if (rc < 0)
|
|
goto buffer_too_small;
|
|
}
|
|
|
|
return 0;
|
|
|
|
buffer_too_small:
|
|
LOGP(DLMGCP, LOGL_ERROR, "SDP messagebuffer too small\n");
|
|
return -1;
|
|
}
|