diff --git a/CHANGES b/CHANGES index 8a387244d7d22a68b5dc88e45abd22b42f71f079..c66bf68ca8c1b0f955bf8839c3077e31b273f604 100644 --- a/CHANGES +++ b/CHANGES @@ -173,6 +173,16 @@ Calendaring for Asterisk iCalendar, CalDAV, and Exchange Server calendars are supported (Exchange support only tested on Exchange Server 2003 with no support for forms-based authentication). +Multicast RTP Support +--------------------- + * A new RTP engine and channel driver have been added which supports Multicast RTP. + The channel driver can be used with the Page application to perform multicast RTP + paging. The dial string format is: MulticastRTP/<type>/<destination>/<control address> + Type can be either basic or linksys. + Destination is the IP address and port for the RTP packets. + Control address is specific to the linksys type and is used for sending the control + packets unique to them. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 1.6.1 to Asterisk 1.6.2 ------------- ------------------------------------------------------------------------------ diff --git a/channels/chan_multicast_rtp.c b/channels/chan_multicast_rtp.c new file mode 100644 index 0000000000000000000000000000000000000000..589fa1008c9438e292e4ce919b4f9e3879370c0b --- /dev/null +++ b/channels/chan_multicast_rtp.c @@ -0,0 +1,184 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * Andreas 'MacBrody' Brodmann <andreas.brodmann@gmail.com> + * + * 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 + * + * \author Joshua Colp <jcolp@digium.com> + * \author Andreas 'MacBrody' Broadmann <andreas.brodmann@gmail.com> + * + * \brief Multicast RTP Paging Channel + * + * \ingroup channel_drivers + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <fcntl.h> +#include <sys/signal.h> + +#include "asterisk/lock.h" +#include "asterisk/channel.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/sched.h" +#include "asterisk/io.h" +#include "asterisk/acl.h" +#include "asterisk/callerid.h" +#include "asterisk/file.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/causes.h" + +static const char tdesc[] = "Multicast RTP Paging Channel Driver"; + +/* Forward declarations */ +static struct ast_channel *multicast_rtp_request(const char *type, int format, void *data, int *cause); +static int multicast_rtp_call(struct ast_channel *ast, char *dest, int timeout); +static int multicast_rtp_hangup(struct ast_channel *ast); +static struct ast_frame *multicast_rtp_read(struct ast_channel *ast); +static int multicast_rtp_write(struct ast_channel *ast, struct ast_frame *f); + +/* Channel driver declaration */ +static const struct ast_channel_tech multicast_rtp_tech = { + .type = "MulticastRTP", + .description = tdesc, + .capabilities = -1, + .requester = multicast_rtp_request, + .call = multicast_rtp_call, + .hangup = multicast_rtp_hangup, + .read = multicast_rtp_read, + .write = multicast_rtp_write, +}; + +/*! \brief Function called when we should read a frame from the channel */ +static struct ast_frame *multicast_rtp_read(struct ast_channel *ast) +{ + return &ast_null_frame; +} + +/*! \brief Function called when we should write a frame to the channel */ +static int multicast_rtp_write(struct ast_channel *ast, struct ast_frame *f) +{ + struct ast_rtp_instance *instance = ast->tech_pvt; + + return ast_rtp_instance_write(instance, f); +} + +/*! \brief Function called when we should actually call the destination */ +static int multicast_rtp_call(struct ast_channel *ast, char *dest, int timeout) +{ + struct ast_rtp_instance *instance = ast->tech_pvt; + + ast_queue_control(ast, AST_CONTROL_ANSWER); + + return ast_rtp_instance_activate(instance); +} + +/*! \brief Function called when we should hang the channel up */ +static int multicast_rtp_hangup(struct ast_channel *ast) +{ + struct ast_rtp_instance *instance = ast->tech_pvt; + + ast_rtp_instance_destroy(instance); + + ast->tech_pvt = NULL; + + return 0; +} + +/*! \brief Function called when we should prepare to call the destination */ +static struct ast_channel *multicast_rtp_request(const char *type, int format, void *data, int *cause) +{ + char *tmp = ast_strdupa(data), *multicast_type = tmp, *destination, *control; + struct ast_rtp_instance *instance; + struct sockaddr_in control_address = { .sin_family = AF_INET, }, destination_address = { .sin_family = AF_INET, }; + struct ast_channel *chan; + int fmt = ast_best_codec(format); + + /* If no type was given we can't do anything */ + if (ast_strlen_zero(multicast_type)) { + goto failure; + } + + if (!(destination = strchr(tmp, '/'))) { + goto failure; + } + *destination++ = '\0'; + + if (ast_parse_arg(destination, PARSE_INADDR | PARSE_PORT_REQUIRE, &destination_address)) { + goto failure; + } + + if ((control = strchr(destination, '/'))) { + *control++ = '\0'; + if (ast_parse_arg(control, PARSE_INADDR | PARSE_PORT_REQUIRE, &control_address)) { + goto failure; + } + } + + if (!(instance = ast_rtp_instance_new("multicast", NULL, &control_address, multicast_type))) { + goto failure; + } + + if (!(chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", 0, "MulticastRTP/%p", instance))) { + ast_rtp_instance_destroy(instance); + goto failure; + } + + ast_rtp_instance_set_remote_address(instance, &destination_address); + + chan->tech = &multicast_rtp_tech; + chan->nativeformats = fmt; + chan->writeformat = fmt; + chan->readformat = fmt; + chan->rawwriteformat = fmt; + chan->rawreadformat = fmt; + chan->tech_pvt = instance; + + return chan; + +failure: + *cause = AST_CAUSE_FAILURE; + return NULL; +} + +/*! \brief Function called when our module is loaded */ +static int load_module(void) +{ + if (ast_channel_register(&multicast_rtp_tech)) { + ast_log(LOG_ERROR, "Unable to register channel class 'MulticastRTP'\n"); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +/*! \brief Function called when our module is unloaded */ +static int unload_module(void) +{ + ast_channel_unregister(&multicast_rtp_tech); + + return 0; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Multicast RTP Paging Channel"); diff --git a/res/res_rtp_multicast.c b/res/res_rtp_multicast.c new file mode 100644 index 0000000000000000000000000000000000000000..3752ebefc856427fe98253223468e0c91d9c1cb8 --- /dev/null +++ b/res/res_rtp_multicast.c @@ -0,0 +1,261 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * Andreas 'MacBrody' Brodmann <andreas.brodmann@gmail.com> + * + * 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 Multicast RTP Engine + * + * \author Joshua Colp <jcolp@digium.com> + * \author Andreas 'MacBrody' Brodmann <andreas.brodmann@gmail.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/time.h> +#include <signal.h> +#include <fcntl.h> +#include <math.h> + +#include "asterisk/pbx.h" +#include "asterisk/frame.h" +#include "asterisk/channel.h" +#include "asterisk/acl.h" +#include "asterisk/config.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/netsock.h" +#include "asterisk/cli.h" +#include "asterisk/manager.h" +#include "asterisk/unaligned.h" +#include "asterisk/module.h" +#include "asterisk/rtp_engine.h" + +/*! Command value used for Linksys paging to indicate we are starting */ +#define LINKSYS_MCAST_STARTCMD 6 + +/*! Command value used for Linksys paging to indicate we are stopping */ +#define LINKSYS_MCAST_STOPCMD 7 + +/*! \brief Type of paging to do */ +enum multicast_type { + /*! Simple multicast enabled client/receiver paging like Snom and Barix uses */ + MULTICAST_TYPE_BASIC = 0, + /*! More advanced Linksys type paging which requires a start and stop packet */ + MULTICAST_TYPE_LINKSYS, +}; + +/*! \brief Structure for a Linksys control packet */ +struct multicast_control_packet { + /*! Unique identifier for the control packet */ + uint32_t unique_id; + /*! Actual command in the control packet */ + uint32_t command; + /*! IP address for the RTP */ + uint32_t ip; + /*! Port for the RTP */ + uint32_t port; +}; + +/*! \brief Structure for a multicast paging instance */ +struct multicast_rtp { + /*! TYpe of multicast paging this instance is doing */ + enum multicast_type type; + /*! Socket used for sending the audio on */ + int socket; + /*! Synchronization source value, used when creating/sending the RTP packet */ + unsigned int ssrc; + /*! Sequence number, used when creating/sending the RTP packet */ + unsigned int seqno; +}; + +/* Forward Declarations */ +static int multicast_rtp_new(struct ast_rtp_instance *instance, struct sched_context *sched, struct sockaddr_in *sin, void *data); +static int multicast_rtp_activate(struct ast_rtp_instance *instance); +static int multicast_rtp_destroy(struct ast_rtp_instance *instance); +static int multicast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *frame); +static struct ast_frame *multicast_rtp_read(struct ast_rtp_instance *instance, int rtcp); + +/* RTP Engine Declaration */ +static struct ast_rtp_engine multicast_rtp_engine = { + .name = "multicast", + .new = multicast_rtp_new, + .activate = multicast_rtp_activate, + .destroy = multicast_rtp_destroy, + .write = multicast_rtp_write, + .read = multicast_rtp_read, +}; + +/*! \brief Function called to create a new multicast instance */ +static int multicast_rtp_new(struct ast_rtp_instance *instance, struct sched_context *sched, struct sockaddr_in *sin, void *data) +{ + struct multicast_rtp *multicast; + const char *type = data; + + if (!(multicast = ast_calloc(1, sizeof(*multicast)))) { + return -1; + } + + if (!strcasecmp(type, "basic")) { + multicast->type = MULTICAST_TYPE_BASIC; + } else if (!strcasecmp(type, "linksys")) { + multicast->type = MULTICAST_TYPE_LINKSYS; + } else { + ast_free(multicast); + return -1; + } + + if ((multicast->socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + ast_free(multicast); + return -1; + } + + multicast->ssrc = ast_random(); + + ast_rtp_instance_set_data(instance, multicast); + + return 0; +} + +/*! \brief Helper function which populates a control packet with useful information and sends it */ +static int multicast_send_control_packet(struct ast_rtp_instance *instance, struct multicast_rtp *multicast, int command) +{ + struct multicast_control_packet control_packet = { .unique_id = htonl((u_long)time(NULL)), + .command = htonl(command), + }; + struct sockaddr_in control_address, remote_address; + + ast_rtp_instance_get_local_address(instance, &control_address); + ast_rtp_instance_get_remote_address(instance, &remote_address); + + /* Ensure the user of us have given us both the control address and destination address */ + if (!control_address.sin_addr.s_addr || !remote_address.sin_addr.s_addr) { + return -1; + } + + control_packet.ip = remote_address.sin_addr.s_addr; + control_packet.port = htonl(ntohs(remote_address.sin_port)); + + /* Based on a recommendation by Brian West who did the FreeSWITCH implementation we send control packets twice */ + sendto(multicast->socket, &control_packet, sizeof(control_packet), 0, (struct sockaddr *)&control_address, sizeof(control_address)); + sendto(multicast->socket, &control_packet, sizeof(control_packet), 0, (struct sockaddr *)&control_address, sizeof(control_address)); + + return 0; +} + +/*! \brief Function called to indicate that audio is now going to flow */ +static int multicast_rtp_activate(struct ast_rtp_instance *instance) +{ + struct multicast_rtp *multicast = ast_rtp_instance_get_data(instance); + + if (multicast->type != MULTICAST_TYPE_LINKSYS) { + return 0; + } + + return multicast_send_control_packet(instance, multicast, LINKSYS_MCAST_STARTCMD); +} + +/*! \brief Function called to destroy a multicast instance */ +static int multicast_rtp_destroy(struct ast_rtp_instance *instance) +{ + struct multicast_rtp *multicast = ast_rtp_instance_get_data(instance); + + if (multicast->type == MULTICAST_TYPE_LINKSYS) { + multicast_send_control_packet(instance, multicast, LINKSYS_MCAST_STOPCMD); + } + + close(multicast->socket); + + ast_free(multicast); + + return 0; +} + +/*! \brief Function called to broadcast some audio on a multicast instance */ +static int multicast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *frame) +{ + struct multicast_rtp *multicast = ast_rtp_instance_get_data(instance); + struct ast_frame *f = frame; + struct sockaddr_in remote_address; + int hdrlen = 12, res, codec; + unsigned char *rtpheader; + + /* We only accept audio, nothing else */ + if (frame->frametype != AST_FRAME_VOICE) { + return 0; + } + + /* Grab the actual payload number for when we create the RTP packet */ + if ((codec = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(instance), 1, frame->subclass)) < 0) { + return -1; + } + + /* If we do not have space to construct an RTP header duplicate the frame so we get some */ + if (frame->offset < hdrlen) { + f = ast_frdup(frame); + } + + /* Construct an RTP header for our packet */ + rtpheader = (unsigned char *)(f->data.ptr - hdrlen); + put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (multicast->seqno++) | (0 << 23))); + put_unaligned_uint32(rtpheader + 4, htonl(f->ts * 8)); + put_unaligned_uint32(rtpheader + 8, htonl(multicast->ssrc)); + + /* Finally send it out to the eager phones listening for us */ + ast_rtp_instance_get_remote_address(instance, &remote_address); + res = sendto(multicast->socket, (void *) rtpheader, f->datalen + hdrlen, 0, (struct sockaddr *) &remote_address, sizeof(remote_address)); + + if (res < 0) { + ast_log(LOG_ERROR, "Multicast RTP Transmission error to %s:%u: %s\n", + ast_inet_ntoa(remote_address.sin_addr), ntohs(remote_address.sin_port), strerror(errno)); + } + + /* If we were forced to duplicate the frame free the new one */ + if (frame != f) { + ast_frfree(f); + } + + return res; +} + +/*! \brief Function called to read from a multicast instance */ +static struct ast_frame *multicast_rtp_read(struct ast_rtp_instance *instance, int rtcp) +{ + return &ast_null_frame; +} + +static int load_module(void) +{ + if (ast_rtp_engine_register(&multicast_rtp_engine)) { + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_rtp_engine_unregister(&multicast_rtp_engine); + + return 0; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Multicast RTP Engine");