mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-04 03:50:31 +00:00
This adds rtpsource options analogous to the rtpdest functions that already exist. In addition, this fixes potential crashes which could result due to trying to read values from nonexistent RTP streams. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@254551 65c4cc65-6c06-0410-ace0-fbb531ad65f3
403 lines
13 KiB
C
403 lines
13 KiB
C
/*
|
|
* Asterisk -- An open source telephony toolkit.
|
|
*
|
|
* Copyright (C) 2010, Digium, Inc.
|
|
*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2. See the LICENSE file
|
|
* at the top of the source tree.
|
|
*/
|
|
|
|
/*!
|
|
* \file
|
|
* \brief sip channel dialplan functions and unit tests
|
|
*/
|
|
|
|
#include "asterisk.h"
|
|
|
|
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|
|
|
#include <math.h>
|
|
|
|
#include "asterisk/channel.h"
|
|
#include "asterisk/rtp_engine.h"
|
|
#include "asterisk/pbx.h"
|
|
#include "asterisk/acl.h"
|
|
|
|
#include "include/sip.h"
|
|
#include "include/globals.h"
|
|
#include "include/dialog.h"
|
|
#include "include/dialplan_functions.h"
|
|
#include "include/sip_utils.h"
|
|
|
|
|
|
int sip_acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen)
|
|
{
|
|
struct sip_pvt *p = chan->tech_pvt;
|
|
char *parse = ast_strdupa(preparse);
|
|
int res = 0;
|
|
AST_DECLARE_APP_ARGS(args,
|
|
AST_APP_ARG(param);
|
|
AST_APP_ARG(type);
|
|
AST_APP_ARG(field);
|
|
);
|
|
AST_STANDARD_APP_ARGS(args, parse);
|
|
|
|
/* Sanity check */
|
|
if (!IS_SIP_TECH(chan->tech)) {
|
|
ast_log(LOG_ERROR, "Cannot call %s on a non-SIP channel\n", funcname);
|
|
return 0;
|
|
}
|
|
|
|
memset(buf, 0, buflen);
|
|
|
|
if (p == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (!strcasecmp(args.param, "peerip")) {
|
|
ast_copy_string(buf, p->sa.sin_addr.s_addr ? ast_inet_ntoa(p->sa.sin_addr) : "", buflen);
|
|
} else if (!strcasecmp(args.param, "recvip")) {
|
|
ast_copy_string(buf, p->recv.sin_addr.s_addr ? ast_inet_ntoa(p->recv.sin_addr) : "", buflen);
|
|
} else if (!strcasecmp(args.param, "from")) {
|
|
ast_copy_string(buf, p->from, buflen);
|
|
} else if (!strcasecmp(args.param, "uri")) {
|
|
ast_copy_string(buf, p->uri, buflen);
|
|
} else if (!strcasecmp(args.param, "useragent")) {
|
|
ast_copy_string(buf, p->useragent, buflen);
|
|
} else if (!strcasecmp(args.param, "peername")) {
|
|
ast_copy_string(buf, p->peername, buflen);
|
|
} else if (!strcasecmp(args.param, "t38passthrough")) {
|
|
ast_copy_string(buf, (p->t38.state == T38_DISABLED) ? "0" : "1", buflen);
|
|
} else if (!strcasecmp(args.param, "rtpdest")) {
|
|
struct sockaddr_in sin;
|
|
struct ast_rtp_instance *stream;
|
|
|
|
if (ast_strlen_zero(args.type))
|
|
args.type = "audio";
|
|
|
|
if (!strcasecmp(args.type, "audio"))
|
|
stream = p->rtp;
|
|
else if (!strcasecmp(args.type, "video"))
|
|
stream = p->vrtp;
|
|
else if (!strcasecmp(args.type, "text"))
|
|
stream = p->trtp;
|
|
else
|
|
return -1;
|
|
|
|
if (!stream) {
|
|
return -1;
|
|
}
|
|
|
|
ast_rtp_instance_get_remote_address(stream, &sin);
|
|
snprintf(buf, buflen, "%s:%d", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
|
|
} else if (!strcasecmp(args.param, "rtpsource")) {
|
|
struct sockaddr_in sin;
|
|
struct ast_rtp_instance *stream;
|
|
|
|
if (ast_strlen_zero(args.type))
|
|
args.type = "audio";
|
|
|
|
if (!strcasecmp(args.type, "audio"))
|
|
stream = p->rtp;
|
|
else if (!strcasecmp(args.type, "video"))
|
|
stream = p->vrtp;
|
|
else if (!strcasecmp(args.type, "text"))
|
|
stream = p->trtp;
|
|
else
|
|
return -1;
|
|
|
|
if (!stream) {
|
|
return -1;
|
|
}
|
|
|
|
ast_rtp_instance_get_local_address(stream, &sin);
|
|
|
|
if (!sin.sin_addr.s_addr) {
|
|
struct sockaddr_in dest_sin;
|
|
ast_rtp_instance_get_remote_address(stream, &dest_sin);
|
|
ast_ouraddrfor(&dest_sin.sin_addr, &sin.sin_addr);
|
|
}
|
|
|
|
snprintf(buf, buflen, "%s:%d", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
|
|
} else if (!strcasecmp(args.param, "rtpqos")) {
|
|
struct ast_rtp_instance *rtp = NULL;
|
|
|
|
if (ast_strlen_zero(args.type)) {
|
|
args.type = "audio";
|
|
}
|
|
|
|
if (!strcasecmp(args.type, "audio")) {
|
|
rtp = p->rtp;
|
|
} else if (!strcasecmp(args.type, "video")) {
|
|
rtp = p->vrtp;
|
|
} else if (!strcasecmp(args.type, "text")) {
|
|
rtp = p->trtp;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
if (ast_strlen_zero(args.field) || !strcasecmp(args.field, "all")) {
|
|
char quality_buf[AST_MAX_USER_FIELD], *quality;
|
|
|
|
if (!(quality = ast_rtp_instance_get_quality(rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
|
|
return -1;
|
|
}
|
|
|
|
ast_copy_string(buf, quality_buf, buflen);
|
|
return res;
|
|
} else {
|
|
struct ast_rtp_instance_stats stats;
|
|
int i;
|
|
struct {
|
|
const char *name;
|
|
enum { INT, DBL } type;
|
|
union {
|
|
unsigned int *i4;
|
|
double *d8;
|
|
};
|
|
} lookup[] = {
|
|
{ "txcount", INT, { .i4 = &stats.txcount, }, },
|
|
{ "rxcount", INT, { .i4 = &stats.rxcount, }, },
|
|
{ "txjitter", INT, { .i4 = &stats.txjitter, }, },
|
|
{ "rxjitter", INT, { .i4 = &stats.rxjitter, }, },
|
|
{ "remote_maxjitter", DBL, { .d8 = &stats.remote_maxjitter, }, },
|
|
{ "remote_minjitter", DBL, { .d8 = &stats.remote_minjitter, }, },
|
|
{ "remote_normdevjitter", DBL, { .d8 = &stats.remote_normdevjitter, }, },
|
|
{ "remote_stdevjitter", DBL, { .d8 = &stats.remote_stdevjitter, }, },
|
|
{ "local_maxjitter", DBL, { .d8 = &stats.local_maxjitter, }, },
|
|
{ "local_minjitter", DBL, { .d8 = &stats.local_minjitter, }, },
|
|
{ "local_normdevjitter", DBL, { .d8 = &stats.local_normdevjitter, }, },
|
|
{ "local_stdevjitter", DBL, { .d8 = &stats.local_stdevjitter, }, },
|
|
{ "txploss", INT, { .i4 = &stats.txploss, }, },
|
|
{ "rxploss", INT, { .i4 = &stats.rxploss, }, },
|
|
{ "remote_maxrxploss", DBL, { .d8 = &stats.remote_maxrxploss, }, },
|
|
{ "remote_minrxploss", DBL, { .d8 = &stats.remote_minrxploss, }, },
|
|
{ "remote_normdevrxploss", DBL, { .d8 = &stats.remote_normdevrxploss, }, },
|
|
{ "remote_stdevrxploss", DBL, { .d8 = &stats.remote_stdevrxploss, }, },
|
|
{ "local_maxrxploss", DBL, { .d8 = &stats.local_maxrxploss, }, },
|
|
{ "local_minrxploss", DBL, { .d8 = &stats.local_minrxploss, }, },
|
|
{ "local_normdevrxploss", DBL, { .d8 = &stats.local_normdevrxploss, }, },
|
|
{ "local_stdevrxploss", DBL, { .d8 = &stats.local_stdevrxploss, }, },
|
|
{ "rtt", INT, { .i4 = &stats.rtt, }, },
|
|
{ "maxrtt", DBL, { .d8 = &stats.maxrtt, }, },
|
|
{ "minrtt", DBL, { .d8 = &stats.minrtt, }, },
|
|
{ "normdevrtt", DBL, { .d8 = &stats.normdevrtt, }, },
|
|
{ "stdevrtt", DBL, { .d8 = &stats.stdevrtt, }, },
|
|
{ "local_ssrc", INT, { .i4 = &stats.local_ssrc, }, },
|
|
{ "remote_ssrc", INT, { .i4 = &stats.remote_ssrc, }, },
|
|
{ NULL, },
|
|
};
|
|
|
|
if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; !ast_strlen_zero(lookup[i].name); i++) {
|
|
if (!strcasecmp(args.field, lookup[i].name)) {
|
|
if (lookup[i].type == INT) {
|
|
snprintf(buf, buflen, "%u", *lookup[i].i4);
|
|
} else {
|
|
snprintf(buf, buflen, "%f", *lookup[i].d8);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", preparse, funcname);
|
|
return -1;
|
|
}
|
|
} else {
|
|
res = -1;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
#ifdef TEST_FRAMEWORK
|
|
static int test_sip_rtpqos_1_new(struct ast_rtp_instance *instance, struct sched_context *sched, struct sockaddr_in *sin, void *data)
|
|
{
|
|
/* Needed to pass sanity checks */
|
|
ast_rtp_instance_set_data(instance, data);
|
|
return 0;
|
|
}
|
|
|
|
static int test_sip_rtpqos_1_destroy(struct ast_rtp_instance *instance)
|
|
{
|
|
/* Needed to pass sanity checks */
|
|
return 0;
|
|
}
|
|
|
|
static struct ast_frame *test_sip_rtpqos_1_read(struct ast_rtp_instance *instance, int rtcp)
|
|
{
|
|
/* Needed to pass sanity checks */
|
|
return &ast_null_frame;
|
|
}
|
|
|
|
static int test_sip_rtpqos_1_write(struct ast_rtp_instance *instance, struct ast_frame *frame)
|
|
{
|
|
/* Needed to pass sanity checks */
|
|
return 0;
|
|
}
|
|
|
|
static int test_sip_rtpqos_1_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_instance_stats *stats, enum ast_rtp_instance_stat stat)
|
|
{
|
|
struct ast_rtp_instance_stats *s = ast_rtp_instance_get_data(instance);
|
|
memcpy(stats, s, sizeof(*stats));
|
|
return 0;
|
|
}
|
|
|
|
AST_TEST_DEFINE(test_sip_rtpqos_1)
|
|
{
|
|
int i, res = AST_TEST_PASS;
|
|
struct ast_rtp_engine test_engine = {
|
|
.name = "test",
|
|
.new = test_sip_rtpqos_1_new,
|
|
.destroy = test_sip_rtpqos_1_destroy,
|
|
.read = test_sip_rtpqos_1_read,
|
|
.write = test_sip_rtpqos_1_write,
|
|
.get_stat = test_sip_rtpqos_1_get_stat,
|
|
};
|
|
struct sockaddr_in sin = { .sin_port = 31337, .sin_addr = { 4 * 16777216 + 3 * 65536 + 2 * 256 + 1 } };
|
|
struct ast_rtp_instance_stats mine = { 0, };
|
|
struct sip_pvt *p = NULL;
|
|
struct ast_channel *chan = NULL;
|
|
struct ast_str *varstr = NULL, *buffer = NULL;
|
|
struct {
|
|
const char *name;
|
|
enum { INT, DBL } type;
|
|
union {
|
|
unsigned int *i4;
|
|
double *d8;
|
|
};
|
|
} lookup[] = {
|
|
{ "txcount", INT, { .i4 = &mine.txcount, }, },
|
|
{ "rxcount", INT, { .i4 = &mine.rxcount, }, },
|
|
{ "txjitter", INT, { .i4 = &mine.txjitter, }, },
|
|
{ "rxjitter", INT, { .i4 = &mine.rxjitter, }, },
|
|
{ "remote_maxjitter", DBL, { .d8 = &mine.remote_maxjitter, }, },
|
|
{ "remote_minjitter", DBL, { .d8 = &mine.remote_minjitter, }, },
|
|
{ "remote_normdevjitter", DBL, { .d8 = &mine.remote_normdevjitter, }, },
|
|
{ "remote_stdevjitter", DBL, { .d8 = &mine.remote_stdevjitter, }, },
|
|
{ "local_maxjitter", DBL, { .d8 = &mine.local_maxjitter, }, },
|
|
{ "local_minjitter", DBL, { .d8 = &mine.local_minjitter, }, },
|
|
{ "local_normdevjitter", DBL, { .d8 = &mine.local_normdevjitter, }, },
|
|
{ "local_stdevjitter", DBL, { .d8 = &mine.local_stdevjitter, }, },
|
|
{ "txploss", INT, { .i4 = &mine.txploss, }, },
|
|
{ "rxploss", INT, { .i4 = &mine.rxploss, }, },
|
|
{ "remote_maxrxploss", DBL, { .d8 = &mine.remote_maxrxploss, }, },
|
|
{ "remote_minrxploss", DBL, { .d8 = &mine.remote_minrxploss, }, },
|
|
{ "remote_normdevrxploss", DBL, { .d8 = &mine.remote_normdevrxploss, }, },
|
|
{ "remote_stdevrxploss", DBL, { .d8 = &mine.remote_stdevrxploss, }, },
|
|
{ "local_maxrxploss", DBL, { .d8 = &mine.local_maxrxploss, }, },
|
|
{ "local_minrxploss", DBL, { .d8 = &mine.local_minrxploss, }, },
|
|
{ "local_normdevrxploss", DBL, { .d8 = &mine.local_normdevrxploss, }, },
|
|
{ "local_stdevrxploss", DBL, { .d8 = &mine.local_stdevrxploss, }, },
|
|
{ "rtt", INT, { .i4 = &mine.rtt, }, },
|
|
{ "maxrtt", DBL, { .d8 = &mine.maxrtt, }, },
|
|
{ "minrtt", DBL, { .d8 = &mine.minrtt, }, },
|
|
{ "normdevrtt", DBL, { .d8 = &mine.normdevrtt, }, },
|
|
{ "stdevrtt", DBL, { .d8 = &mine.stdevrtt, }, },
|
|
{ "local_ssrc", INT, { .i4 = &mine.local_ssrc, }, },
|
|
{ "remote_ssrc", INT, { .i4 = &mine.remote_ssrc, }, },
|
|
{ NULL, },
|
|
};
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = "test_sip_rtpqos";
|
|
info->category = "channels/chan_sip/";
|
|
info->summary = "Test retrieval of SIP RTP QOS stats";
|
|
info->description =
|
|
"Verify values in the RTP instance structure can be accessed through the dialplan.";
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
ast_rtp_engine_register2(&test_engine, NULL);
|
|
/* Have to associate this with a SIP pvt and an ast_channel */
|
|
if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL))) {
|
|
res = AST_TEST_NOT_RUN;
|
|
goto done;
|
|
}
|
|
if (!(p->rtp = ast_rtp_instance_new("test", sched, &bindaddr, &mine))) {
|
|
res = AST_TEST_NOT_RUN;
|
|
goto done;
|
|
}
|
|
ast_rtp_instance_set_remote_address(p->rtp, &sin);
|
|
if (!(chan = ast_dummy_channel_alloc())) {
|
|
res = AST_TEST_NOT_RUN;
|
|
goto done;
|
|
}
|
|
chan->tech = &sip_tech;
|
|
chan->tech_pvt = p;
|
|
p->owner = chan;
|
|
|
|
varstr = ast_str_create(16);
|
|
buffer = ast_str_create(16);
|
|
if (!varstr || !buffer) {
|
|
res = AST_TEST_NOT_RUN;
|
|
goto done;
|
|
}
|
|
|
|
/* Populate "mine" with values, then retrieve them with the CHANNEL dialplan function */
|
|
for (i = 0; !ast_strlen_zero(lookup[i].name); i++) {
|
|
ast_str_set(&varstr, 0, "${CHANNEL(rtpqos,audio,%s)}", lookup[i].name);
|
|
if (lookup[i].type == INT) {
|
|
int j;
|
|
char cmpstr[256];
|
|
for (j = 1; j < 25; j++) {
|
|
*lookup[i].i4 = j;
|
|
ast_str_substitute_variables(&buffer, 0, chan, ast_str_buffer(varstr));
|
|
snprintf(cmpstr, sizeof(cmpstr), "%d", j);
|
|
if (strcmp(cmpstr, ast_str_buffer(buffer))) {
|
|
res = AST_TEST_FAIL;
|
|
ast_test_status_update(test, "%s != %s != %s\n", ast_str_buffer(varstr), cmpstr, ast_str_buffer(buffer));
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
double j, cmpdbl = 0.0;
|
|
for (j = 1.0; j < 10.0; j += 0.3) {
|
|
*lookup[i].d8 = j;
|
|
ast_str_substitute_variables(&buffer, 0, chan, ast_str_buffer(varstr));
|
|
if (sscanf(ast_str_buffer(buffer), "%lf", &cmpdbl) != 1 || fabs(j - cmpdbl > .05)) {
|
|
res = AST_TEST_FAIL;
|
|
ast_test_status_update(test, "%s != %f != %s\n", ast_str_buffer(varstr), j, ast_str_buffer(buffer));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
ast_free(varstr);
|
|
ast_free(buffer);
|
|
|
|
/* This unref will take care of destroying the channel, RTP instance, and SIP pvt */
|
|
if (p) {
|
|
dialog_unref(p, "Destroy test object");
|
|
}
|
|
ast_rtp_engine_unregister(&test_engine);
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
/*! \brief SIP test registration */
|
|
void sip_dialplan_function_register_tests(void)
|
|
{
|
|
AST_TEST_REGISTER(test_sip_rtpqos_1);
|
|
}
|
|
|
|
/*! \brief SIP test registration */
|
|
void sip_dialplan_function_unregister_tests(void)
|
|
{
|
|
AST_TEST_UNREGISTER(test_sip_rtpqos_1);
|
|
}
|
|
|