Get trunk to compile
[asterisk/asterisk.git] / apps / app_rtppage.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Andreas 'MacBrody' Brodmann
5  *
6  * Andreas 'MacBrody' Brodmann <andreas.brodmann@gmail.com>
7  *
8  * Information on how multicast paging works with linksys 
9  * phones was used from FreeSWITCH's mod_esf with permission
10  * from Brian West.
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  * This program is free software, distributed under the terms of
19  * the GNU General Public License Version 2. See the LICENSE file
20  * at the top of the source tree.
21  */
22
23 /*! \file
24  *
25  * \brief Application to stream a channel's input to a specified uni-/multicast address
26  *
27  * \author Andreas 'MacBrody' Brodmann <andreas.brodmann@gmail.com>
28  * 
29  * \ingroup applications
30  */
31
32 /*** MODULEINFO
33         <defaultenabled>yes</defaultenabled>
34  ***/
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <string.h>
44 #include <sys/types.h>
45 #include <sys/socket.h>
46 #include <netdb.h>
47 #include <netinet/in.h>
48 #include <arpa/inet.h>
49
50 #include "asterisk/file.h"
51 #include "asterisk/logger.h"
52 #include "asterisk/channel.h"
53 #include "asterisk/pbx.h"
54 #include "asterisk/module.h"
55 #include "asterisk/lock.h"
56 #include "asterisk/app.h"
57 #include "asterisk/config.h"
58 #include "asterisk/acl.h"
59
60 #define RTP_PT_ULAW    0
61 #define RTP_PT_GSM     3
62 #define RTP_PT_ALAW    8
63 #define RTP_PT_G729   18
64
65 /*! \brief Multicast Group Receiver Type object */
66 enum grouptype {
67         MGT_BASIC = 1,    /*!< simple multicast enabled client/receiver like snom, barix */
68         MGT_LINKSYS = 2,  /*!< linksys ipphones; they need a start/stop packet */
69         MGT_CISCO = 3     /*!< cisco phones; they need a http request to their internal web server // NOT YET IMPLEMENTED */
70 };
71
72 /*! \brief Multicast Group object */
73 struct mcast_group {
74         char name[32];                        /*!< name of the group */
75         enum grouptype type;                  /*!< type, see grouptype */
76         int socket;                           /*!< socket used for streaming to this group (each group has got its own socket */
77         int ttl;                              /*!< timetolive to be set on this socket */
78         struct sockaddr_in rtp_address;       /*!< address/port pair where the traffic is sent to */
79         struct sockaddr_in control_address;   /*!< address/port for linksys phones to send the start/stop packet to */
80         AST_LIST_ENTRY(mcast_group) list;     /*!< next element int group list */
81 };
82
83 /*! \brief RTP header object */
84 struct rtp_header {
85         uint16_t flags;
86         uint16_t seqno;
87         uint32_t timestamp;
88         uint32_t ssrc;
89 };
90
91 /*! \brief Control Packet object as used for linksys phones for start/stop packets */
92 struct control_packet {
93         uint32_t unique_id;                    /*!< unique id per command start or stop - not the same for both commands */
94         uint32_t command;                      /*!< the command: 6=start, 7=stop */
95         uint32_t ip;                           /*!< multicast address in network byte order */
96         uint32_t port;                         /*!< udp port to send the data to */
97 };
98
99 /*! \brief List to hold all the multicast groups defined in the config file */
100 static AST_LIST_HEAD_STATIC(groups, mcast_group);
101
102 static char *app = "RTPPage";
103 static char *synopsis = "RTPPage Application";
104 static char *descrip = "  RTPPage(direct|multicast, ip:port[&ip:port]|group[&group2[&group3...]][,codec]): Sends the channel's input to the\n"
105 "specified group(s) defined in the config file rtppage.conf.\n"
106 "The optional codec may be one of the following:\n"
107 "   ulaw - default\n"
108 "   alaw\n"
109 "   gsm\n"
110 "   g729\n"
111 "as long as asterisk does not have to translate or respective translators are\n"
112 "installed with your asterisk installation. If none or any other codec is\n"
113 "specified the application will fall back to ulaw.\n";
114
115 static const char config[] = "rtppage.conf";
116 static int default_ttl = -1;
117 static unsigned int tos = -1;
118
119 /*! \brief Read input from channel and send it to the specified group(s) as rtp traffic */
120 static int rtppage_exec(struct ast_channel *chan, void *data)
121 {
122         int res = 0;
123         struct ast_module_user *u =  NULL;
124         struct ast_frame *f = NULL;
125         char *parse = NULL;
126         char *rest = NULL, *cur = NULL;
127         char *rest2 = NULL;
128         char *ip = NULL, *port = NULL;
129         int ms = -1;
130         unsigned char *databuf = NULL;
131         struct sockaddr_in destaddr;
132         struct mcast_group *group;
133         struct control_packet cpk;
134         struct rtp_header *rtph = NULL;
135         uint8_t rtp_pt = RTP_PT_ULAW;
136         int chan_format = AST_FORMAT_ULAW;
137         uint16_t rtpflags = 0;
138         int ttl = 0;
139         int pagetype = 0;
140         /* you can specify three arguments:
141          * 1) pagetype (0 = direct, 1 = multicast)
142          * 2) groups, e.g. NameOfGroup or Name1&Name2 etc) / or ip:port in case of direct
143          * 3) optional: codec, if specified and valid
144          *    this codec will be used for streaming
145          */
146         AST_DECLARE_APP_ARGS(args,
147                 AST_APP_ARG(pagetype);
148                 AST_APP_ARG(groups);
149                 AST_APP_ARG(codec);
150         );
151         AST_LIST_HEAD(, mcast_group) activegroups;
152
153         AST_LIST_HEAD_INIT(&activegroups);
154
155         /* make sure there is at least one parameter */
156         if (ast_strlen_zero(data)) {
157                 ast_log(LOG_WARNING, "%s requires argument (group(s)[,codec])\n", app);
158                 return -1;
159         }
160
161         parse = ast_strdupa(data);
162         AST_STANDARD_APP_ARGS(args, parse);
163
164         /* pagetype is a mandatory parameter */
165         if (!args.pagetype) {
166                 ast_log(LOG_WARNING, "%s requires arguments (pagetype, group(s) | ip:port[,codec])\n", app);
167                 return(-1);
168         }
169         if (!strcasecmp(args.pagetype, "direct")) {
170                 pagetype = 0;
171         } else if (!strcasecmp(args.pagetype, "multicast")) {
172                 pagetype = 1;
173         } else {
174                 ast_log(LOG_ERROR, "%s is an invalid grouptype! valid types are: direct, multicast.\n", args.pagetype);
175                 return(-1);
176         }
177
178         /* group is a mandatory parameter */
179         if (!args.groups) {
180                 ast_log(LOG_WARNING, "%s requires arguments (pagetype, group(s) | ip:port[,codec])\n", app);
181                 return(-1);
182         }
183
184         /* setup variables for the desired codec */
185         if (args.codec) {
186                 if (!strcasecmp(args.codec, "ulaw")) {
187                         /* use default settings */
188                 } else if (!strcasecmp(args.codec, "alaw")) {
189                         rtp_pt = RTP_PT_ALAW;
190                         chan_format = AST_FORMAT_ALAW;
191                 } else if (!strcasecmp(args.codec, "gsm")) {
192                         rtp_pt = RTP_PT_GSM;
193                         chan_format = AST_FORMAT_GSM;
194                 } else if (!strcasecmp(args.codec, "g729")) {
195                         rtp_pt = RTP_PT_G729;
196                         chan_format = AST_FORMAT_G729A;
197                 } else {
198                         /* use ulaw as fallback */
199                         rtp_pt = RTP_PT_ULAW;
200                         chan_format = AST_FORMAT_ULAW;
201                 }
202         }
203
204         u = ast_module_user_add(chan);
205
206         /* Check if the channel is answered, if not
207          * do answer it */
208         if (chan->_state != AST_STATE_UP) {
209                 res = ast_answer(chan);
210                 if (res) {
211                         ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
212                         goto end;
213                 }
214         }
215
216         /* allocate memory for the rtp send buffer */
217         if ((databuf = ast_calloc(1, 172)) == NULL) {
218                 ast_log(LOG_WARNING, "Failed to allocate memory for the data buffer, give up\n");
219                 goto end;
220         }
221
222         /* initialize rtp buffer header
223          * with rtp version and
224          * payload type
225          */
226         rtph = (struct rtp_header *)databuf;
227         rtpflags  = (0x02 << 14); /* rtp v2 */
228         rtpflags  = (rtpflags & 0xFF80) |  rtp_pt;  
229         rtph->flags = htons(rtpflags);
230         rtph->ssrc =  htonl((u_long)time(NULL));
231         
232         /* first create a temporary table for this page session
233          * containing all groups which will be used
234          */
235         AST_LIST_LOCK(&groups);
236         rest = ast_strdup(args.groups);
237         if (pagetype == 0) {
238                 /* a direct page call. this can actually be used
239                  * for multicast paging too by passing the ip:port as
240                  * argument 2 
241                  */
242                 while ((cur = strsep(&rest, "&"))) {
243                         struct mcast_group *agroup = ast_calloc(1, sizeof(*agroup));
244                         rest2 = ast_strdup(cur);
245                         ip = strsep(&rest2, ":");
246                         port = strsep(&rest2, ":");
247                         if (ip == NULL || port == NULL) {
248                                 ast_log(LOG_WARNING, "invalid ip:port pair in call to RTPPage (%s)!\n", cur);
249                                 free(agroup);
250                                 continue;
251                         }
252                         agroup->rtp_address.sin_family = AF_INET;
253                         agroup->rtp_address.sin_port = htons(atoi(port));
254                         if (inet_pton(AF_INET, ip, &agroup->rtp_address.sin_addr) <= 0) {
255                                 ast_log(LOG_WARNING, "invalid ip in call to RTPPage (%s)!\n", cur);
256                                 free(agroup);
257                                 continue;
258                         }
259                         agroup->type = MGT_BASIC;
260                         agroup->socket = -1;
261                         agroup->ttl = -1;
262                         AST_LIST_INSERT_TAIL(&activegroups, agroup, list);
263                 }
264         } else if (pagetype == 1) {
265                 /* a multicast page call */
266                 while ((cur = strsep(&rest, "&"))) {
267                         AST_LIST_TRAVERSE(&groups, group, list) {
268                                 if (!strcasecmp(group->name, cur)) {
269                                         struct mcast_group *agroup = ast_calloc(1, sizeof(*agroup));
270                                         memcpy(agroup->name, group->name, 32);
271                                         agroup->type = group->type;
272                                         agroup->socket = group->socket;
273                                         agroup->ttl = group->ttl;
274                                         memcpy(&agroup->rtp_address, &group->rtp_address, sizeof(agroup->rtp_address));
275                                         memcpy(&agroup->control_address, &group->control_address, sizeof(agroup->control_address));
276                                         AST_LIST_INSERT_TAIL(&activegroups, agroup, list);
277                                 }
278                         }
279                 }
280         }
281         AST_LIST_UNLOCK(&groups);
282
283         /* now initialize these groups, e.g. create a udp socket for each,
284          * set ttl and tos if requested by config, and
285          * in case of linksys type groups send the multicast start signal
286          */
287         AST_LIST_TRAVERSE(&activegroups, group, list) {
288                 group->socket = socket(AF_INET, SOCK_DGRAM, 0);
289                 /* set ttl if configured
290                  * ttl can be configured either globally in the
291                  * category 'general' or locally within
292                  * the respective groups
293                  */
294                 if (group->ttl >= 0 || default_ttl >= 0) {
295                         ttl = default_ttl;
296                         if (group->ttl >= 0) {
297                                 ttl = group->ttl;
298                         }
299                         if (setsockopt(group->socket, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
300                                 ast_log(LOG_WARNING, "Failed to set ttl on socket for group %s!\n", group->name);
301                         }
302                 }
303                 /* set tos if requested 
304                  * tos can only be configured globally ('general')
305                  */
306                 if (tos >= 0) {
307                         if (setsockopt(group->socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
308                                 ast_log(LOG_WARNING, "Failed to set tos field on socket for group %s!\n", group->name);
309                         }
310                 }
311                 /* for linksys device groups send multicast start command */
312                 if (group->type == MGT_LINKSYS) {
313                         cpk.unique_id = htonl((u_long)time(NULL));
314                         cpk.command = htonl(6);  /* multicast start command */
315                         memcpy(&cpk.ip, &group->rtp_address.sin_addr, sizeof(cpk.ip));
316                         cpk.port = htonl(ntohs(group->rtp_address.sin_port));
317                         memcpy(&destaddr, &group->control_address, sizeof(destaddr));
318                         sendto(group->socket, &cpk, sizeof(cpk), 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
319                         sendto(group->socket, &cpk, sizeof(cpk), 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
320                 }
321         }
322
323         /* Set read format as configured - this codec will be used for streaming */
324         res = ast_set_read_format(chan, chan_format);
325         if (res < 0) {
326                 ast_log(LOG_WARNING, "Unable to set channel read mode, giving up\n");
327                 res = -1;
328                 goto end;
329         }
330
331         /* Play a beep to let the caller know he can start talking */
332         res = ast_streamfile(chan, "beep", chan->language);
333         if (!res) {
334                 res = ast_waitstream(chan, "");
335         } else {
336                 ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
337         }
338         ast_stopstream(chan);
339
340         /* main loop: 
341          * read frames from the input channel and, if they are voice frames,
342          * send them to all requested multi-/unicast listeners.
343          */
344         for (;;) {
345                 ms = ast_waitfor(chan, 1000);
346                 if (ms < 0) {
347                         ast_log(LOG_DEBUG, "Hangup detected\n");
348                         goto end;
349                 }
350                 f = ast_read(chan);
351                 if (!f)
352                         break;
353
354                 /* if the speaker pressed '#', then quit */
355                 if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
356                         res = 0;
357                         ast_frfree(f);
358                         ast_log(LOG_DEBUG, "Received DTMF key: %d\n", f->subclass);
359                         goto end;
360                 }
361
362                 if (f->frametype == AST_FRAME_VOICE) {
363                         /* update the rtp header */
364                         rtph = (struct rtp_header *)databuf;
365                         rtph->seqno = htons(f->seqno);
366                         rtph->timestamp = htonl(f->ts * 8);
367                         memcpy(databuf+12, f->data, f->datalen);
368
369                         /* now send that frame to the destination groups */
370                         AST_LIST_TRAVERSE(&activegroups, group, list) {
371                                 memcpy(&destaddr, &group->rtp_address, sizeof(destaddr));
372                                 if (sendto(group->socket, databuf, f->datalen+12, 0, (struct sockaddr *)&destaddr, sizeof(destaddr)) <= 0) {
373                                         ast_log(LOG_DEBUG, "sendto() failed!\n");
374                                 }
375                         }
376                 }
377                 ast_frfree(f);
378                 f = NULL;
379         }
380
381 end:
382
383         /* send a stop multicast signal to all linksys devices */
384         AST_LIST_TRAVERSE(&activegroups, group, list) {
385                 if (group->socket > 0) {
386                         if (group->type == MGT_LINKSYS) {
387                                 cpk.unique_id = htonl((u_long)time(NULL));
388                                 cpk.command = htonl(7);  /* multicast stop command */
389                                 memcpy(&cpk.ip, &group->rtp_address.sin_addr, sizeof(cpk.ip));
390                                 cpk.port = htonl(ntohs(group->rtp_address.sin_port));
391                                 memcpy(&destaddr, &group->control_address, sizeof(destaddr));
392                                 sendto(group->socket, &cpk, 8, 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
393                                 sendto(group->socket, &cpk, 8, 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
394                         }
395                         close(group->socket);
396                 }
397         }
398
399         /* free activegroups list */
400         while ((group = AST_LIST_REMOVE_HEAD(&activegroups, list))) {
401                 free(group);
402         }
403
404         /* free the rtp data buffer */
405         if (databuf != NULL) {
406                 free(databuf);
407         }
408
409         ast_module_user_remove(u);
410         ast_log(LOG_DEBUG, "Exit RTPPage(%s)\n", args.groups);
411
412         return res;
413 }
414
415 static int load_config(int reload) {
416
417         int res = 0;
418         const char *cat = NULL;
419         struct ast_config *cfg = NULL;
420         struct mcast_group *group = NULL;
421         const char *var = NULL;
422         struct ast_flags config_flags = { 0 };
423
424         AST_LIST_LOCK(&groups);
425         if (reload) {
426                 /* if this is a reload, then free the config structure before
427                  * filling it again 
428                  */
429                 while ((group = AST_LIST_REMOVE_HEAD(&groups, list))) {
430                         free(group);
431                 }
432
433                 /* reset default_ttl & tos */
434                 default_ttl = -1; /* means not set */
435                 tos = -1;
436         }
437
438         /* load config file */
439         if (!(cfg = ast_config_load(config, config_flags))) {
440                 ast_log(LOG_NOTICE, "Failed to load config!\n");
441                 AST_LIST_UNLOCK(&groups);
442                 return(-1);
443         }
444
445         while ((cat = ast_category_browse(cfg, cat)) != NULL) {
446                 /* 'general' is reserved for generic options */
447                 if (!strcasecmp(cat, "general")) {
448                         var = ast_variable_retrieve(cfg, cat, "ttl");
449                         if (var) {
450                                 default_ttl = atoi(var);
451                         }
452                         var = ast_variable_retrieve(cfg, cat, "tos");
453                         if (var) {
454                                 ast_str2tos(var, &tos);
455                         }
456                         continue;
457                 }
458
459                 group = ast_calloc(1, sizeof(*group));
460                 var = ast_variable_retrieve(cfg, cat, "type");
461                 if (!strcasecmp(var, "basic")) {
462                         ast_copy_string(group->name, cat, sizeof(group->name));
463                         group->type = MGT_BASIC;
464                         group->socket = -1;
465                         group->ttl = -1;
466                         if (ast_variable_retrieve(cfg, cat, "ttl") != NULL) {
467                                 group->ttl = atoi(ast_variable_retrieve(cfg, cat, "ttl"));
468                         }
469                         memset(&group->rtp_address, 0, sizeof(group->rtp_address));
470                         group->rtp_address.sin_family = AF_INET;
471                         group->rtp_address.sin_port = htons(atoi(ast_variable_retrieve(cfg, cat, "rtp_port")));
472                         if (inet_pton(AF_INET, ast_variable_retrieve(cfg, cat, "rtp_address"), &group->rtp_address.sin_addr) <= 0) {
473                                 ast_log(LOG_NOTICE, "Invalid ip address in group %s!\n", cat);
474                                 ast_free(group);
475                                 group = NULL;
476                                 continue;
477                         }
478                 } else if (!strcasecmp(var, "linksys")) {
479                         ast_copy_string(group->name, cat, sizeof(group->name));
480                         group->type = MGT_LINKSYS;
481                         group->socket = -1;
482                         group->ttl = -1;
483                         if (ast_variable_retrieve(cfg, cat, "ttl") != NULL) {
484                                 group->ttl = atoi(ast_variable_retrieve(cfg, cat, "ttl"));
485                         }
486                         memset(&group->rtp_address, 0, sizeof(group->rtp_address));
487                         group->rtp_address.sin_family = AF_INET;
488                         group->rtp_address.sin_port = htons(atoi(ast_variable_retrieve(cfg, cat, "rtp_port")));
489                         if (inet_pton(AF_INET, ast_variable_retrieve(cfg, cat, "rtp_address"), &group->rtp_address.sin_addr) <= 0) {
490                                 ast_log(LOG_NOTICE, "Invalid ip address in group %s!\n", cat);
491                                 ast_free(group);
492                                 group = NULL;
493                                 continue;
494                         }
495                         memset(&group->control_address, 0, sizeof(group->control_address));
496                         group->control_address.sin_family = AF_INET;
497                         group->control_address.sin_port = htons(atoi(ast_variable_retrieve(cfg, cat, "control_port")));
498                         if (inet_pton(AF_INET, ast_variable_retrieve(cfg, cat, "control_address"), &group->control_address.sin_addr) <= 0) {
499                                 ast_log(LOG_NOTICE, "Invalid ip address in group %s!\n", cat);
500                                 ast_free(group);
501                                 group = NULL;
502                                 continue;
503                         }
504                 } else {
505                         group->type = -1;
506                         group->socket = -1;
507                         group->ttl = -1;
508                         ast_log(LOG_NOTICE, "Invalid mcast group %s!\n", cat);
509                         continue;
510                 }
511
512                 /* now add it to the linked list */
513                 AST_LIST_INSERT_TAIL(&groups, group, list);
514                 ast_log(LOG_NOTICE, "loaded category %s\n", group->name);
515                 group = NULL;
516                 var = NULL;
517         }
518
519         AST_LIST_UNLOCK(&groups);
520
521         ast_config_destroy(cfg);
522
523         return(res);
524 }
525
526 static int unload_module(void)
527 {
528         int res;
529         res = ast_unregister_application(app);
530         ast_module_user_hangup_all();
531         return res;     
532 }
533
534 static int load_module(void)
535 {
536
537         load_config(0);
538         return ast_register_application(app, rtppage_exec, synopsis, descrip);
539 }
540
541 static int reload(void)
542 {
543         return load_config(1);
544 }
545
546 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "RTPPage Application",
547         .load = load_module,
548         .unload = unload_module,
549         .reload = reload,
550 );
551
552