Add rtppage() application to do multicast or unicast RTP paging to SIP phones.
[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         AST_LIST_HEAD(, mcast_group) activegroups;
141
142         /* init active groups */
143         activegroups.first = NULL;
144         activegroups.last = NULL;
145         activegroups.lock = AST_MUTEX_INIT_VALUE;
146
147         /* you can specify three arguments:
148          * 1) pagetype (0 = direct, 1 = multicast)
149          * 2) groups, e.g. NameOfGroup or Name1&Name2 etc) / or ip:port in case of direct
150          * 3) optional: codec, if specified and valid
151          *    this codec will be used for streaming
152          */
153         AST_DECLARE_APP_ARGS(args,
154                 AST_APP_ARG(pagetype);
155                 AST_APP_ARG(groups);
156                 AST_APP_ARG(codec);
157         );
158
159         /* make sure there is at least one parameter */
160         if (ast_strlen_zero(data)) {
161                 ast_log(LOG_WARNING, "%s requires argument (group(s)[,codec])\n", app);
162                 return -1;
163         }
164
165         parse = ast_strdupa(data);
166         AST_STANDARD_APP_ARGS(args, parse);
167
168         /* pagetype is a mandatory parameter */
169         if (!args.pagetype) {
170                 ast_log(LOG_WARNING, "%s requires arguments (pagetype, group(s) | ip:port[,codec])\n", app);
171                 return(-1);
172         }
173         if (!strcasecmp(args.pagetype, "direct")) {
174                 pagetype = 0;
175         } else if (!strcasecmp(args.pagetype, "multicast")) {
176                 pagetype = 1;
177         } else {
178                 ast_log(LOG_ERROR, "%s is an invalid grouptype! valid types are: direct, multicast.\n", args.pagetype);
179                 return(-1);
180         }
181
182         /* group is a mandatory parameter */
183         if (!args.groups) {
184                 ast_log(LOG_WARNING, "%s requires arguments (pagetype, group(s) | ip:port[,codec])\n", app);
185                 return(-1);
186         }
187
188         /* setup variables for the desired codec */
189         if (args.codec) {
190                 if (!strcasecmp(args.codec, "ulaw")) {
191                         /* use default settings */
192                 } else if (!strcasecmp(args.codec, "alaw")) {
193                         rtp_pt = RTP_PT_ALAW;
194                         chan_format = AST_FORMAT_ALAW;
195                 } else if (!strcasecmp(args.codec, "gsm")) {
196                         rtp_pt = RTP_PT_GSM;
197                         chan_format = AST_FORMAT_GSM;
198                 } else if (!strcasecmp(args.codec, "g729")) {
199                         rtp_pt = RTP_PT_G729;
200                         chan_format = AST_FORMAT_G729A;
201                 } else {
202                         /* use ulaw as fallback */
203                         rtp_pt = RTP_PT_ULAW;
204                         chan_format = AST_FORMAT_ULAW;
205                 }
206         }
207
208         u = ast_module_user_add(chan);
209
210         /* Check if the channel is answered, if not
211          * do answer it */
212         if (chan->_state != AST_STATE_UP) {
213                 res = ast_answer(chan);
214                 if (res) {
215                         ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
216                         goto end;
217                 }
218         }
219
220         /* allocate memory for the rtp send buffer */
221         if ((databuf = ast_calloc(1, 172)) == NULL) {
222                 ast_log(LOG_WARNING, "Failed to allocate memory for the data buffer, give up\n");
223                 goto end;
224         }
225
226         /* initialize rtp buffer header
227          * with rtp version and
228          * payload type
229          */
230         rtph = (struct rtp_header *)databuf;
231         rtpflags  = (0x02 << 14); /* rtp v2 */
232         rtpflags  = (rtpflags & 0xFF80) |  rtp_pt;  
233         rtph->flags = htons(rtpflags);
234         rtph->ssrc =  htonl((u_long)time(NULL));
235         
236         /* first create a temporary table for this page session
237          * containing all groups which will be used
238          */
239         AST_LIST_LOCK(&groups);
240         rest = ast_strdup(args.groups);
241         if (pagetype == 0) {
242                 /* a direct page call. this can actually be used
243                  * for multicast paging too by passing the ip:port as
244                  * argument 2 
245                  */
246                 while ((cur = strsep(&rest, "&"))) {
247                         struct mcast_group *agroup = ast_calloc(1, sizeof(*agroup));
248                         rest2 = ast_strdup(cur);
249                         ip = strsep(&rest2, ":");
250                         port = strsep(&rest2, ":");
251                         if (ip == NULL || port == NULL) {
252                                 ast_log(LOG_WARNING, "invalid ip:port pair in call to RTPPage (%s)!\n", cur);
253                                 free(agroup);
254                                 continue;
255                         }
256                         agroup->rtp_address.sin_family = AF_INET;
257                         agroup->rtp_address.sin_port = htons(atoi(port));
258                         if (inet_pton(AF_INET, ip, &agroup->rtp_address.sin_addr) <= 0) {
259                                 ast_log(LOG_WARNING, "invalid ip in call to RTPPage (%s)!\n", cur);
260                                 free(agroup);
261                                 continue;
262                         }
263                         agroup->type = MGT_BASIC;
264                         agroup->socket = -1;
265                         agroup->ttl = -1;
266                         AST_LIST_INSERT_TAIL(&activegroups, agroup, list);
267                 }
268         } else if (pagetype == 1) {
269                 /* a multicast page call */
270                 while ((cur = strsep(&rest, "&"))) {
271                         AST_LIST_TRAVERSE(&groups, group, list) {
272                                 if (!strcasecmp(group->name, cur)) {
273                                         struct mcast_group *agroup = ast_calloc(1, sizeof(*agroup));
274                                         memcpy(agroup->name, group->name, 32);
275                                         agroup->type = group->type;
276                                         agroup->socket = group->socket;
277                                         agroup->ttl = group->ttl;
278                                         memcpy(&agroup->rtp_address, &group->rtp_address, sizeof(agroup->rtp_address));
279                                         memcpy(&agroup->control_address, &group->control_address, sizeof(agroup->control_address));
280                                         AST_LIST_INSERT_TAIL(&activegroups, agroup, list);
281                                 }
282                         }
283                 }
284         }
285         AST_LIST_UNLOCK(&groups);
286
287         /* now initialize these groups, e.g. create a udp socket for each,
288          * set ttl and tos if requested by config, and
289          * in case of linksys type groups send the multicast start signal
290          */
291         AST_LIST_TRAVERSE(&activegroups, group, list) {
292                 group->socket = socket(AF_INET, SOCK_DGRAM, 0);
293                 /* set ttl if configured
294                  * ttl can be configured either globally in the
295                  * category 'general' or locally within
296                  * the respective groups
297                  */
298                 if (group->ttl >= 0 || default_ttl >= 0) {
299                         ttl = default_ttl;
300                         if (group->ttl >= 0) {
301                                 ttl = group->ttl;
302                         }
303                         if (setsockopt(group->socket, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
304                                 ast_log(LOG_WARNING, "Failed to set ttl on socket for group %s!\n", group->name);
305                         }
306                 }
307                 /* set tos if requested 
308                  * tos can only be configured globally ('general')
309                  */
310                 if (tos >= 0) {
311                         if (setsockopt(group->socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
312                                 ast_log(LOG_WARNING, "Failed to set tos field on socket for group %s!\n", group->name);
313                         }
314                 }
315                 /* for linksys device groups send multicast start command */
316                 if (group->type == MGT_LINKSYS) {
317                         cpk.unique_id = htonl((u_long)time(NULL));
318                         cpk.command = htonl(6);  /* multicast start command */
319                         memcpy(&cpk.ip, &group->rtp_address.sin_addr, sizeof(cpk.ip));
320                         cpk.port = htonl(ntohs(group->rtp_address.sin_port));
321                         memcpy(&destaddr, &group->control_address, sizeof(destaddr));
322                         sendto(group->socket, &cpk, sizeof(cpk), 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
323                         sendto(group->socket, &cpk, sizeof(cpk), 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
324                 }
325         }
326
327         /* Set read format as configured - this codec will be used for streaming */
328         res = ast_set_read_format(chan, chan_format);
329         if (res < 0) {
330                 ast_log(LOG_WARNING, "Unable to set channel read mode, giving up\n");
331                 res = -1;
332                 goto end;
333         }
334
335         /* Play a beep to let the caller know he can start talking */
336         res = ast_streamfile(chan, "beep", chan->language);
337         if (!res) {
338                 res = ast_waitstream(chan, "");
339         } else {
340                 ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
341         }
342         ast_stopstream(chan);
343
344         /* main loop: 
345          * read frames from the input channel and, if they are voice frames,
346          * send them to all requested multi-/unicast listeners.
347          */
348         for (;;) {
349                 ms = ast_waitfor(chan, 1000);
350                 if (ms < 0) {
351                         ast_log(LOG_DEBUG, "Hangup detected\n");
352                         goto end;
353                 }
354                 f = ast_read(chan);
355                 if (!f)
356                         break;
357
358                 /* if the speaker pressed '#', then quit */
359                 if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
360                         res = 0;
361                         ast_frfree(f);
362                         ast_log(LOG_DEBUG, "Received DTMF key: %d\n", f->subclass);
363                         goto end;
364                 }
365
366                 if (f->frametype == AST_FRAME_VOICE) {
367                         /* update the rtp header */
368                         rtph = (struct rtp_header *)databuf;
369                         rtph->seqno = htons(f->seqno);
370                         rtph->timestamp = htonl(f->ts * 8);
371                         memcpy(databuf+12, f->data, f->datalen);
372
373                         /* now send that frame to the destination groups */
374                         AST_LIST_TRAVERSE(&activegroups, group, list) {
375                                 memcpy(&destaddr, &group->rtp_address, sizeof(destaddr));
376                                 if (sendto(group->socket, databuf, f->datalen+12, 0, (struct sockaddr *)&destaddr, sizeof(destaddr)) <= 0) {
377                                         ast_log(LOG_DEBUG, "sendto() failed!\n");
378                                 }
379                         }
380                 }
381                 ast_frfree(f);
382                 f = NULL;
383         }
384
385 end:
386
387         /* send a stop multicast signal to all linksys devices */
388         AST_LIST_TRAVERSE(&activegroups, group, list) {
389                 if (group->socket > 0) {
390                         if (group->type == MGT_LINKSYS) {
391                                 cpk.unique_id = htonl((u_long)time(NULL));
392                                 cpk.command = htonl(7);  /* multicast stop command */
393                                 memcpy(&cpk.ip, &group->rtp_address.sin_addr, sizeof(cpk.ip));
394                                 cpk.port = htonl(ntohs(group->rtp_address.sin_port));
395                                 memcpy(&destaddr, &group->control_address, sizeof(destaddr));
396                                 sendto(group->socket, &cpk, 8, 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
397                                 sendto(group->socket, &cpk, 8, 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
398                         }
399                         close(group->socket);
400                 }
401         }
402
403         /* free activegroups list */
404         while ((group = AST_LIST_REMOVE_HEAD(&activegroups, list))) {
405                 free(group);
406         }
407
408         /* free the rtp data buffer */
409         if (databuf != NULL) {
410                 free(databuf);
411         }
412
413         ast_module_user_remove(u);
414         ast_log(LOG_DEBUG, "Exit RTPPage(%s)\n", args.groups);
415
416         return res;
417 }
418
419 static int load_config(int reload) {
420
421         int res = 0;
422         const char *cat = NULL;
423         struct ast_config *cfg = NULL;
424         struct mcast_group *group = NULL;
425         const char *var = NULL;
426         struct ast_flags config_flags = { 0 };
427
428         AST_LIST_LOCK(&groups);
429         if (reload) {
430                 /* if this is a reload, then free the config structure before
431                  * filling it again 
432                  */
433                 while ((group = AST_LIST_REMOVE_HEAD(&groups, list))) {
434                         free(group);
435                 }
436
437                 /* reset default_ttl & tos */
438                 default_ttl = -1; /* means not set */
439                 tos = -1;
440         }
441
442         /* load config file */
443         if (!(cfg = ast_config_load(config, config_flags))) {
444                 ast_log(LOG_NOTICE, "Failed to load config!\n");
445                 AST_LIST_UNLOCK(&groups);
446                 return(-1);
447         }
448
449         while ((cat = ast_category_browse(cfg, cat)) != NULL) {
450                 /* 'general' is reserved for generic options */
451                 if (!strcasecmp(cat, "general")) {
452                         var = ast_variable_retrieve(cfg, cat, "ttl");
453                         if (var) {
454                                 default_ttl = atoi(var);
455                         }
456                         var = ast_variable_retrieve(cfg, cat, "tos");
457                         if (var) {
458                                 ast_str2tos(var, &tos);
459                         }
460                         continue;
461                 }
462
463                 group = ast_calloc(1, sizeof(*group));
464                 var = ast_variable_retrieve(cfg, cat, "type");
465                 if (!strcasecmp(var, "basic")) {
466                         ast_copy_string(group->name, cat, sizeof(group->name));
467                         group->type = MGT_BASIC;
468                         group->socket = -1;
469                         group->ttl = -1;
470                         if (ast_variable_retrieve(cfg, cat, "ttl") != NULL) {
471                                 group->ttl = atoi(ast_variable_retrieve(cfg, cat, "ttl"));
472                         }
473                         memset(&group->rtp_address, 0, sizeof(group->rtp_address));
474                         group->rtp_address.sin_family = AF_INET;
475                         group->rtp_address.sin_port = htons(atoi(ast_variable_retrieve(cfg, cat, "rtp_port")));
476                         if (inet_pton(AF_INET, ast_variable_retrieve(cfg, cat, "rtp_address"), &group->rtp_address.sin_addr) <= 0) {
477                                 ast_log(LOG_NOTICE, "Invalid ip address in group %s!\n", cat);
478                                 ast_free(group);
479                                 group = NULL;
480                                 continue;
481                         }
482                 } else if (!strcasecmp(var, "linksys")) {
483                         ast_copy_string(group->name, cat, sizeof(group->name));
484                         group->type = MGT_LINKSYS;
485                         group->socket = -1;
486                         group->ttl = -1;
487                         if (ast_variable_retrieve(cfg, cat, "ttl") != NULL) {
488                                 group->ttl = atoi(ast_variable_retrieve(cfg, cat, "ttl"));
489                         }
490                         memset(&group->rtp_address, 0, sizeof(group->rtp_address));
491                         group->rtp_address.sin_family = AF_INET;
492                         group->rtp_address.sin_port = htons(atoi(ast_variable_retrieve(cfg, cat, "rtp_port")));
493                         if (inet_pton(AF_INET, ast_variable_retrieve(cfg, cat, "rtp_address"), &group->rtp_address.sin_addr) <= 0) {
494                                 ast_log(LOG_NOTICE, "Invalid ip address in group %s!\n", cat);
495                                 ast_free(group);
496                                 group = NULL;
497                                 continue;
498                         }
499                         memset(&group->control_address, 0, sizeof(group->control_address));
500                         group->control_address.sin_family = AF_INET;
501                         group->control_address.sin_port = htons(atoi(ast_variable_retrieve(cfg, cat, "control_port")));
502                         if (inet_pton(AF_INET, ast_variable_retrieve(cfg, cat, "control_address"), &group->control_address.sin_addr) <= 0) {
503                                 ast_log(LOG_NOTICE, "Invalid ip address in group %s!\n", cat);
504                                 ast_free(group);
505                                 group = NULL;
506                                 continue;
507                         }
508                 } else {
509                         group->type = -1;
510                         group->socket = -1;
511                         group->ttl = -1;
512                         ast_log(LOG_NOTICE, "Invalid mcast group %s!\n", cat);
513                         continue;
514                 }
515
516                 /* now add it to the linked list */
517                 AST_LIST_INSERT_TAIL(&groups, group, list);
518                 ast_log(LOG_NOTICE, "loaded category %s\n", group->name);
519                 group = NULL;
520                 var = NULL;
521         }
522
523         AST_LIST_UNLOCK(&groups);
524
525         ast_config_destroy(cfg);
526
527         return(res);
528 }
529
530 static int unload_module(void)
531 {
532         int res;
533         res = ast_unregister_application(app);
534         ast_module_user_hangup_all();
535         return res;     
536 }
537
538 static int load_module(void)
539 {
540
541         load_config(0);
542         return ast_register_application(app, rtppage_exec, synopsis, descrip);
543 }
544
545 static int reload(void)
546 {
547         return load_config(1);
548 }
549
550 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "RTPPage Application",
551         .load = load_module,
552         .unload = unload_module,
553         .reload = reload,
554 );
555
556