osmo-mgw/src/libosmo-mgcp/mgcp_sdp.c

638 lines
16 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/core/socket.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/mgcp/mgcp.h>
#include <osmocom/mgcp/osmux.h>
#include <osmocom/mgcp/mgcp_conn.h>
#include <osmocom/mgcp/mgcp_protocol.h>
#include <osmocom/mgcp/mgcp_msg.h>
#include <osmocom/mgcp/mgcp_endp.h>
#include <osmocom/mgcp/mgcp_trunk.h>
#include <osmocom/mgcp/mgcp_codec.h>
#include <osmocom/mgcp/mgcp_sdp.h>
#include <osmocom/mgcp/mgcp_protocol.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>
/* Two structs to store intermediate parsing results. The function
* mgcp_parse_sdp_data() is using the following two structs 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;
};
struct sdp_fmtp_param {
int payload_type;
struct mgcp_codec_param param;
};
/* 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;
char *pt_end;
unsigned long 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;
errno = 0;
pt = strtoul(pt_str, &pt_end, 0);
if ((errno == ERANGE && pt == ULONG_MAX) || (errno && !pt) ||
pt_str == pt_end)
goto error;
if (pt >> 7) /* PT is 7 bit field, higher values not allowed */
goto error;
/* 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;
}
/* Extract fmtp parameters from SDP, called from: mgcp_parse_sdp_data() */
static int fmtp_from_sdp(void *ctx, struct sdp_fmtp_param *fmtp_param, char *sdp)
{
char *str;
char *str_ptr;
char *param_str;
unsigned int pt;
unsigned int count = 0;
char delimiter;
unsigned int amr_octet_aligned;
memset(fmtp_param, 0, sizeof(*fmtp_param));
str = talloc_zero_size(ctx, strlen(sdp) + 1);
str_ptr = str;
strcpy(str_ptr, sdp);
/* Check if the input string begins with an fmtp token */
str_ptr = strstr(str_ptr, "fmtp:");
if (!str_ptr)
goto exit;
str_ptr += 5;
/* Extract payload type */
if (sscanf(str_ptr, "%u ", &pt) != 1)
goto error;
fmtp_param->payload_type = pt;
/* Advance pointer to the beginning of the parameter section and
* tokenize string */
str_ptr = strstr(str_ptr, " ");
if (!str_ptr)
goto error;
str_ptr++;
param_str = strtok(str_ptr, " ");
if (!param_str)
goto exit;
while (1) {
/* Make sure that we don't get trapped in an endless loop */
if (count > 256)
goto error;
/* Chop off delimiters ';' at the end */
delimiter = str_ptr[strlen(str_ptr) - 1];
if (delimiter == ';' || delimiter == ',')
str_ptr[strlen(str_ptr) - 1] = '\0';
/* AMR octet aligned parameter */
if (sscanf(param_str, "octet-align=%d", &amr_octet_aligned) == 1) {
fmtp_param->param.amr_octet_aligned_present = true;
fmtp_param->param.amr_octet_aligned = false;
if (amr_octet_aligned == 1)
fmtp_param->param.amr_octet_aligned = true;
}
param_str = strtok(NULL, " ");
if (!param_str)
break;
count++;
}
exit:
talloc_free(str);
return 0;
error:
talloc_free(str);
return -EINVAL;
}
static int audio_ip_from_sdp(struct osmo_sockaddr *dst_addr, char *sdp)
{
bool is_ipv6;
char ipbuf[INET6_ADDRSTRLEN];
if (strncmp("c=IN IP", sdp, 7) != 0)
return -1;
sdp += 7;
if (*sdp == '6')
is_ipv6 = true;
else if (*sdp == '4')
is_ipv6 = false;
else
return -1;
sdp++;
if (*sdp != ' ')
return -1;
sdp++;
if (is_ipv6) {
/* 45 = INET6_ADDRSTRLEN -1 */
if (sscanf(sdp, "%45s", ipbuf) != 1)
return -1;
if (inet_pton(AF_INET6, ipbuf, &dst_addr->u.sin6.sin6_addr) != 1)
return -1;
dst_addr->u.sa.sa_family = AF_INET6;
} else {
/* 15 = INET_ADDRSTRLEN -1 */
if (sscanf(sdp, "%15s", ipbuf) != 1)
return -1;
if (inet_pton(AF_INET, ipbuf, &dst_addr->u.sin.sin_addr) != 1)
return -1;
dst_addr->u.sa.sa_family = AF_INET;
}
return 0;
}
/* Pick optional fmtp parameters by payload type, if there are no fmtp
* parameters, a nullpointer is returned */
static struct mgcp_codec_param *param_by_pt(int pt, struct sdp_fmtp_param *fmtp_params, unsigned int fmtp_params_len)
{
unsigned int i;
for (i = 0; i < fmtp_params_len; i++) {
if (fmtp_params[i].payload_type == pt)
return &fmtp_params[i].param;
}
return NULL;
}
/*! 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;
struct sdp_fmtp_param fmtp_params[MGCP_MAX_CODECS];
unsigned int fmtp_used = 0;
struct mgcp_codec_param *codec_param;
char ipbuf[INET6_ADDRSTRLEN];
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;
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);
break;
}
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;
break;
}
if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) {
rtp->maximum_packet_time = ptime2;
break;
}
if (strncmp("a=fmtp:", line, 6) == 0) {
rc = fmtp_from_sdp(conn->conn, &fmtp_params[fmtp_used], line);
if (rc >= 0)
fmtp_used++;
break;
}
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 (audio_ip_from_sdp(&rtp->addr, line) < 0)
return -1;
break;
default:
if (endp)
/* TODO: Check spec: We used the bare endpoint number before,
* now we use the endpoint name as a whole? Is this allowed? */
LOGP(DLMGCP, LOGL_NOTICE,
"Unhandled SDP option: '%c'/%d on %s\n",
line[0], line[0], endp->name);
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++) {
codec_param = param_by_pt(codecs[i].payload_type, fmtp_params, fmtp_used);
rc = mgcp_codec_add(conn, codecs[i].payload_type, codecs[i].map_line, codec_param);
if (rc < 0)
LOGPENDP(endp, DLMGCP, LOGL_NOTICE, "failed to add codec\n");
}
talloc_free(tmp_ctx);
LOGPCONN(conn->conn, DLMGCP, LOGL_NOTICE,
"Got media info via SDP: port:%d, addr:%s, duration:%d, payload-types:",
ntohs(rtp->rtp_port), osmo_sockaddr_ntop(&rtp->addr.u.sa, ipbuf),
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;
}
/* Add rtpmap string to the sdp payload, but only when the payload type falls
* into the dynamic payload type range */
static int add_rtpmap(struct msgb *sdp, int payload_type, const char *audio_name)
{
int rc;
if (payload_type >= 96 && payload_type <= 127) {
if (!audio_name)
return -EINVAL;
rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n", payload_type, audio_name);
if (rc < 0)
return -EINVAL;
}
return 0;
}
/* Add audio strings to sdp payload */
static int add_audio(struct msgb *sdp, int *payload_types, unsigned int payload_types_len, int local_port)
{
int rc;
unsigned int i;
rc = msgb_printf(sdp, "m=audio %d RTP/AVP", local_port);
if (rc < 0)
return -EINVAL;
for (i = 0; i < payload_types_len; i++) {
rc = msgb_printf(sdp, " %d", payload_types[i]);
if (rc < 0)
return -EINVAL;
}
rc = msgb_printf(sdp, "\r\n");
if (rc < 0)
return -EINVAL;
return 0;
}
/* Add fmtp strings to sdp payload */
static int add_fmtp(struct msgb *sdp, struct sdp_fmtp_param *fmtp_params, unsigned int fmtp_params_len,
const char *fmtp_extra)
{
unsigned int i;
int rc;
int fmtp_extra_pt = -1;
char *fmtp_extra_pars = "";
/* When no fmtp parameters ara available but an fmtp extra string
* is configured, just add the fmtp extra string */
if (fmtp_params_len == 0 && fmtp_extra) {
return msgb_printf(sdp, "%s\r\n", fmtp_extra);
}
/* When there is fmtp extra configured we dissect it in order to drop
* in the configured extra parameters at the right place when
* generating the fmtp strings. */
if (fmtp_extra) {
if (sscanf(fmtp_extra, "a=fmtp:%d ", &fmtp_extra_pt) != 1)
fmtp_extra_pt = -1;
fmtp_extra_pars = strstr(fmtp_extra, " ");
if (!fmtp_extra_pars)
fmtp_extra_pars = "";
else
fmtp_extra_pars++;
}
for (i = 0; i < fmtp_params_len; i++) {
rc = msgb_printf(sdp, "a=fmtp:%u", fmtp_params[i].payload_type);
if (rc < 0)
return -EINVAL;
/* Add amr octet align parameter */
if (fmtp_params[i].param.amr_octet_aligned_present) {
if (fmtp_params[i].param.amr_octet_aligned)
rc = msgb_printf(sdp, " octet-align=1");
else
rc = msgb_printf(sdp, " octet-align=0");
if (rc < 0)
return -EINVAL;
}
/* Append extra parameters from fmtp extra */
if (fmtp_params[i].payload_type == fmtp_extra_pt) {
rc = msgb_printf(sdp, " %s", fmtp_extra_pars);
if (rc < 0)
return -EINVAL;
}
rc = msgb_printf(sdp, "\r\n");
if (rc < 0)
return -EINVAL;
}
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 struct mgcp_rtp_codec *codec;
const char *fmtp_extra;
const char *audio_name;
int payload_type;
struct sdp_fmtp_param fmtp_param;
int rc;
int payload_types[1];
int local_port;
struct sdp_fmtp_param fmtp_params[1];
unsigned int fmtp_params_len = 0;
bool addr_is_v6;
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,
&codec, &fmtp_extra,
(struct mgcp_conn_rtp *)conn);
audio_name = codec->audio_name;
payload_type = codec->payload_type;
addr_is_v6 = osmo_ip_str_type(addr) == AF_INET6;
rc = msgb_printf(sdp,
"v=0\r\n"
"o=- %s 23 IN IP%c %s\r\n"
"s=-\r\n"
"c=IN IP%c %s\r\n"
"t=0 0\r\n", conn->conn->id,
addr_is_v6 ? '6' : '4', addr,
addr_is_v6 ? '6' : '4', addr);
if (rc < 0)
goto buffer_too_small;
if (payload_type >= 0) {
payload_types[0] = payload_type;
if (mgcp_conn_rtp_is_osmux(conn))
local_port = endp->cfg->osmux_port;
else
local_port = conn->end.local_port;
rc = add_audio(sdp, payload_types, 1, local_port);
if (rc < 0)
goto buffer_too_small;
if (endp->trunk->audio_send_name) {
rc = add_rtpmap(sdp, payload_type, audio_name);
if (rc < 0)
goto buffer_too_small;
}
if (codec->param_present) {
fmtp_param.payload_type = payload_type;
fmtp_param.param = codec->param;
fmtp_params[0] = fmtp_param;
fmtp_params_len = 1;
}
rc = add_fmtp(sdp, fmtp_params, fmtp_params_len, fmtp_extra);
if (rc < 0)
goto buffer_too_small;
}
if (conn->end.packet_duration_ms > 0 && endp->trunk->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:
LOGPCONN(conn->conn, DLMGCP, LOGL_ERROR, "SDP messagebuffer too small\n");
return -1;
}