2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \author Mark Spencer <markster@digium.com>
23 * \brief Local Proxy Channel
25 * \ingroup channel_drivers
30 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
35 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include <sys/signal.h>
44 #include "asterisk/lock.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/config.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/module.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/options.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/sched.h"
53 #include "asterisk/io.h"
54 #include "asterisk/rtp.h"
55 #include "asterisk/acl.h"
56 #include "asterisk/callerid.h"
57 #include "asterisk/file.h"
58 #include "asterisk/cli.h"
59 #include "asterisk/app.h"
60 #include "asterisk/musiconhold.h"
61 #include "asterisk/manager.h"
62 #include "asterisk/stringfields.h"
63 #include "asterisk/devicestate.h"
65 static const char tdesc
[] = "Local Proxy Channel Driver";
67 #define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
69 static struct ast_channel
*local_request(const char *type
, int format
, void *data
, int *cause
);
70 static int local_digit_begin(struct ast_channel
*ast
, char digit
);
71 static int local_digit_end(struct ast_channel
*ast
, char digit
, unsigned int duration
);
72 static int local_call(struct ast_channel
*ast
, char *dest
, int timeout
);
73 static int local_hangup(struct ast_channel
*ast
);
74 static int local_answer(struct ast_channel
*ast
);
75 static struct ast_frame
*local_read(struct ast_channel
*ast
);
76 static int local_write(struct ast_channel
*ast
, struct ast_frame
*f
);
77 static int local_indicate(struct ast_channel
*ast
, int condition
, const void *data
, size_t datalen
);
78 static int local_fixup(struct ast_channel
*oldchan
, struct ast_channel
*newchan
);
79 static int local_sendhtml(struct ast_channel
*ast
, int subclass
, const char *data
, int datalen
);
80 static int local_sendtext(struct ast_channel
*ast
, const char *text
);
81 static int local_devicestate(void *data
);
83 /* PBX interface structure for channel registration */
84 static const struct ast_channel_tech local_tech
= {
88 .requester
= local_request
,
89 .send_digit_begin
= local_digit_begin
,
90 .send_digit_end
= local_digit_end
,
92 .hangup
= local_hangup
,
93 .answer
= local_answer
,
96 .write_video
= local_write
,
97 .exception
= local_read
,
98 .indicate
= local_indicate
,
100 .send_html
= local_sendhtml
,
101 .send_text
= local_sendtext
,
102 .devicestate
= local_devicestate
,
106 ast_mutex_t lock
; /* Channel private lock */
107 unsigned int flags
; /* Private flags */
108 char context
[AST_MAX_CONTEXT
]; /* Context to call */
109 char exten
[AST_MAX_EXTENSION
]; /* Extension to call */
110 int reqformat
; /* Requested format */
111 struct ast_channel
*owner
; /* Master Channel */
112 struct ast_channel
*chan
; /* Outbound channel */
113 struct ast_module_user
*u_owner
; /*! reference to keep the module loaded while in use */
114 struct ast_module_user
*u_chan
; /*! reference to keep the module loaded while in use */
115 AST_LIST_ENTRY(local_pvt
) list
; /* Next entity */
118 #define LOCAL_GLARE_DETECT (1 << 0) /*!< Detect glare on hangup */
119 #define LOCAL_CANCEL_QUEUE (1 << 1) /*!< Cancel queue */
120 #define LOCAL_ALREADY_MASQED (1 << 2) /*!< Already masqueraded */
121 #define LOCAL_LAUNCHED_PBX (1 << 3) /*!< PBX was launched */
122 #define LOCAL_NO_OPTIMIZATION (1 << 4) /*!< Do not optimize using masquerading */
124 static AST_LIST_HEAD_STATIC(locals
, local_pvt
);
126 /*! \brief Adds devicestate to local channels */
127 static int local_devicestate(void *data
)
129 char *exten
= ast_strdupa(data
);
130 char *context
= NULL
, *opts
= NULL
;
133 if (!(context
= strchr(exten
, '@'))) {
134 ast_log(LOG_WARNING
, "Someone used Local/%s somewhere without a @context. This is bad.\n", exten
);
135 return AST_DEVICE_INVALID
;
140 /* Strip options if they exist */
141 if ((opts
= strchr(context
, '/')))
144 if (option_debug
> 2)
145 ast_log(LOG_DEBUG
, "Checking if extension %s@%s exists (devicestate)\n", exten
, context
);
146 res
= ast_exists_extension(NULL
, context
, exten
, 1, NULL
);
148 return AST_DEVICE_INVALID
;
150 return AST_DEVICE_UNKNOWN
;
153 static int local_queue_frame(struct local_pvt
*p
, int isoutbound
, struct ast_frame
*f
, struct ast_channel
*us
)
155 struct ast_channel
*other
= NULL
;
159 /* Recalculate outbound channel */
160 other
= isoutbound
? p
->owner
: p
->chan
;
162 /* Set glare detection */
163 ast_set_flag(p
, LOCAL_GLARE_DETECT
);
164 if (ast_test_flag(p
, LOCAL_CANCEL_QUEUE
)) {
165 /* We had a glare on the hangup. Forget all this business,
166 return and destroy p. */
167 ast_mutex_unlock(&p
->lock
);
168 ast_mutex_destroy(&p
->lock
);
173 ast_clear_flag(p
, LOCAL_GLARE_DETECT
);
176 if (ast_mutex_trylock(&other
->lock
)) {
177 /* Failed to lock. Release main lock and try again */
178 ast_mutex_unlock(&p
->lock
);
180 if (ast_mutex_unlock(&us
->lock
)) {
181 ast_log(LOG_WARNING
, "%s wasn't locked while sending %d/%d\n",
182 us
->name
, f
->frametype
, f
->subclass
);
186 /* Wait just a bit */
188 /* Only we can destroy ourselves, so we can't disappear here */
190 ast_mutex_lock(&us
->lock
);
191 ast_mutex_lock(&p
->lock
);
194 ast_queue_frame(other
, f
);
195 ast_mutex_unlock(&other
->lock
);
196 ast_clear_flag(p
, LOCAL_GLARE_DETECT
);
200 static int local_answer(struct ast_channel
*ast
)
202 struct local_pvt
*p
= ast
->tech_pvt
;
209 ast_mutex_lock(&p
->lock
);
210 isoutbound
= IS_OUTBOUND(ast
, p
);
212 /* Pass along answer since somebody answered us */
213 struct ast_frame answer
= { AST_FRAME_CONTROL
, AST_CONTROL_ANSWER
};
214 res
= local_queue_frame(p
, isoutbound
, &answer
, ast
);
216 ast_log(LOG_WARNING
, "Huh? Local is being asked to answer?\n");
218 ast_mutex_unlock(&p
->lock
);
222 static void check_bridge(struct local_pvt
*p
, int isoutbound
)
224 if (ast_test_flag(p
, LOCAL_ALREADY_MASQED
) || ast_test_flag(p
, LOCAL_NO_OPTIMIZATION
) || !p
->chan
|| !p
->owner
|| (p
->chan
->_bridge
!= ast_bridged_channel(p
->chan
)))
227 /* only do the masquerade if we are being called on the outbound channel,
228 if it has been bridged to another channel and if there are no pending
229 frames on the owner channel (because they would be transferred to the
230 outbound channel during the masquerade)
232 if (isoutbound
&& p
->chan
->_bridge
/* Not ast_bridged_channel! Only go one step! */ && AST_LIST_EMPTY(&p
->owner
->readq
)) {
233 /* Masquerade bridged channel into owner */
234 /* Lock everything we need, one by one, and give up if
235 we can't get everything. Remember, we'll get another
236 chance in just a little bit */
237 if (!ast_mutex_trylock(&(p
->chan
->_bridge
)->lock
)) {
238 if (!p
->chan
->_bridge
->_softhangup
) {
239 if (!ast_mutex_trylock(&p
->owner
->lock
)) {
240 if (!p
->owner
->_softhangup
) {
241 ast_channel_masquerade(p
->owner
, p
->chan
->_bridge
);
242 ast_set_flag(p
, LOCAL_ALREADY_MASQED
);
244 ast_mutex_unlock(&p
->owner
->lock
);
246 ast_mutex_unlock(&(p
->chan
->_bridge
)->lock
);
249 /* We only allow masquerading in one 'direction'... it's important to preserve the state
250 (group variables, etc.) that live on p->chan->_bridge (and were put there by the dialplan)
251 when the local channels go away.
254 } else if (!isoutbound
&& p
->owner
&& p
->owner
->_bridge
&& p
->chan
&& AST_LIST_EMPTY(&p
->chan
->readq
)) {
255 /* Masquerade bridged channel into chan */
256 if (!ast_mutex_trylock(&(p
->owner
->_bridge
)->lock
)) {
257 if (!p
->owner
->_bridge
->_softhangup
) {
258 if (!ast_mutex_trylock(&p
->chan
->lock
)) {
259 if (!p
->chan
->_softhangup
) {
260 ast_channel_masquerade(p
->chan
, p
->owner
->_bridge
);
261 ast_set_flag(p
, LOCAL_ALREADY_MASQED
);
263 ast_mutex_unlock(&p
->chan
->lock
);
266 ast_mutex_unlock(&(p
->owner
->_bridge
)->lock
);
272 static struct ast_frame
*local_read(struct ast_channel
*ast
)
274 return &ast_null_frame
;
277 static int local_write(struct ast_channel
*ast
, struct ast_frame
*f
)
279 struct local_pvt
*p
= ast
->tech_pvt
;
286 /* Just queue for delivery to the other side */
287 ast_mutex_lock(&p
->lock
);
288 isoutbound
= IS_OUTBOUND(ast
, p
);
289 if (f
&& (f
->frametype
== AST_FRAME_VOICE
|| f
->frametype
== AST_FRAME_VIDEO
))
290 check_bridge(p
, isoutbound
);
291 if (!ast_test_flag(p
, LOCAL_ALREADY_MASQED
))
292 res
= local_queue_frame(p
, isoutbound
, f
, ast
);
295 ast_log(LOG_DEBUG
, "Not posting to queue since already masked on '%s'\n", ast
->name
);
299 ast_mutex_unlock(&p
->lock
);
303 static int local_fixup(struct ast_channel
*oldchan
, struct ast_channel
*newchan
)
305 struct local_pvt
*p
= newchan
->tech_pvt
;
310 ast_mutex_lock(&p
->lock
);
312 if ((p
->owner
!= oldchan
) && (p
->chan
!= oldchan
)) {
313 ast_log(LOG_WARNING
, "Old channel wasn't %p but was %p/%p\n", oldchan
, p
->owner
, p
->chan
);
314 ast_mutex_unlock(&p
->lock
);
317 if (p
->owner
== oldchan
)
321 ast_mutex_unlock(&p
->lock
);
325 static int local_indicate(struct ast_channel
*ast
, int condition
, const void *data
, size_t datalen
)
327 struct local_pvt
*p
= ast
->tech_pvt
;
329 struct ast_frame f
= { AST_FRAME_CONTROL
, };
335 /* If this is an MOH hold or unhold, do it on the Local channel versus real channel */
336 if (condition
== AST_CONTROL_HOLD
) {
337 ast_moh_start(ast
, data
, NULL
);
338 } else if (condition
== AST_CONTROL_UNHOLD
) {
341 /* Queue up a frame representing the indication as a control frame */
342 ast_mutex_lock(&p
->lock
);
343 isoutbound
= IS_OUTBOUND(ast
, p
);
344 f
.subclass
= condition
;
345 f
.data
= (void*)data
;
347 if (!(res
= local_queue_frame(p
, isoutbound
, &f
, ast
)))
348 ast_mutex_unlock(&p
->lock
);
354 static int local_digit_begin(struct ast_channel
*ast
, char digit
)
356 struct local_pvt
*p
= ast
->tech_pvt
;
358 struct ast_frame f
= { AST_FRAME_DTMF_BEGIN
, };
364 ast_mutex_lock(&p
->lock
);
365 isoutbound
= IS_OUTBOUND(ast
, p
);
367 if (!(res
= local_queue_frame(p
, isoutbound
, &f
, ast
)))
368 ast_mutex_unlock(&p
->lock
);
373 static int local_digit_end(struct ast_channel
*ast
, char digit
, unsigned int duration
)
375 struct local_pvt
*p
= ast
->tech_pvt
;
377 struct ast_frame f
= { AST_FRAME_DTMF_END
, };
383 ast_mutex_lock(&p
->lock
);
384 isoutbound
= IS_OUTBOUND(ast
, p
);
387 if (!(res
= local_queue_frame(p
, isoutbound
, &f
, ast
)))
388 ast_mutex_unlock(&p
->lock
);
393 static int local_sendtext(struct ast_channel
*ast
, const char *text
)
395 struct local_pvt
*p
= ast
->tech_pvt
;
397 struct ast_frame f
= { AST_FRAME_TEXT
, };
403 ast_mutex_lock(&p
->lock
);
404 isoutbound
= IS_OUTBOUND(ast
, p
);
405 f
.data
= (char *) text
;
406 f
.datalen
= strlen(text
) + 1;
407 if (!(res
= local_queue_frame(p
, isoutbound
, &f
, ast
)))
408 ast_mutex_unlock(&p
->lock
);
412 static int local_sendhtml(struct ast_channel
*ast
, int subclass
, const char *data
, int datalen
)
414 struct local_pvt
*p
= ast
->tech_pvt
;
416 struct ast_frame f
= { AST_FRAME_HTML
, };
422 ast_mutex_lock(&p
->lock
);
423 isoutbound
= IS_OUTBOUND(ast
, p
);
424 f
.subclass
= subclass
;
425 f
.data
= (char *)data
;
427 if (!(res
= local_queue_frame(p
, isoutbound
, &f
, ast
)))
428 ast_mutex_unlock(&p
->lock
);
432 /*! \brief Initiate new call, part of PBX interface
433 * dest is the dial string */
434 static int local_call(struct ast_channel
*ast
, char *dest
, int timeout
)
436 struct local_pvt
*p
= ast
->tech_pvt
;
438 struct ast_var_t
*varptr
= NULL
, *new;
444 ast_mutex_lock(&p
->lock
);
447 * Note that cid_num and cid_name aren't passed in the ast_channel_alloc
448 * call, so it's done here instead.
450 p
->chan
->cid
.cid_num
= ast_strdup(p
->owner
->cid
.cid_num
);
451 p
->chan
->cid
.cid_name
= ast_strdup(p
->owner
->cid
.cid_name
);
452 p
->chan
->cid
.cid_rdnis
= ast_strdup(p
->owner
->cid
.cid_rdnis
);
453 p
->chan
->cid
.cid_ani
= ast_strdup(p
->owner
->cid
.cid_ani
);
454 p
->chan
->cid
.cid_pres
= p
->owner
->cid
.cid_pres
;
455 ast_string_field_set(p
->chan
, language
, p
->owner
->language
);
456 ast_string_field_set(p
->chan
, accountcode
, p
->owner
->accountcode
);
457 p
->chan
->cdrflags
= p
->owner
->cdrflags
;
459 /* copy the channel variables from the incoming channel to the outgoing channel */
460 /* Note that due to certain assumptions, they MUST be in the same order */
461 AST_LIST_TRAVERSE(&p
->owner
->varshead
, varptr
, entries
) {
462 namelen
= strlen(varptr
->name
);
463 len
= sizeof(struct ast_var_t
) + namelen
+ strlen(varptr
->value
) + 2;
464 if ((new = ast_calloc(1, len
))) {
465 memcpy(new, varptr
, len
);
466 new->value
= &(new->name
[0]) + namelen
+ 1;
467 AST_LIST_INSERT_TAIL(&p
->chan
->varshead
, new, entries
);
471 /* Start switch on sub channel */
472 if (!(res
= ast_pbx_start(p
->chan
)))
473 ast_set_flag(p
, LOCAL_LAUNCHED_PBX
);
475 ast_mutex_unlock(&p
->lock
);
479 /*! \brief Hangup a call through the local proxy channel */
480 static int local_hangup(struct ast_channel
*ast
)
482 struct local_pvt
*p
= ast
->tech_pvt
;
484 struct ast_frame f
= { AST_FRAME_CONTROL
, AST_CONTROL_HANGUP
};
485 struct ast_channel
*ochan
= NULL
;
486 int glaredetect
= 0, res
= 0;
491 ast_mutex_lock(&p
->lock
);
492 isoutbound
= IS_OUTBOUND(ast
, p
);
494 const char *status
= pbx_builtin_getvar_helper(p
->chan
, "DIALSTATUS");
495 if ((status
) && (p
->owner
))
496 pbx_builtin_setvar_helper(p
->owner
, "CHANLOCALSTATUS", status
);
498 ast_clear_flag(p
, LOCAL_LAUNCHED_PBX
);
499 ast_module_user_remove(p
->u_chan
);
502 ast_module_user_remove(p
->u_owner
);
505 ast
->tech_pvt
= NULL
;
507 if (!p
->owner
&& !p
->chan
) {
508 /* Okay, done with the private part now, too. */
509 glaredetect
= ast_test_flag(p
, LOCAL_GLARE_DETECT
);
510 /* If we have a queue holding, don't actually destroy p yet, but
511 let local_queue do it. */
513 ast_set_flag(p
, LOCAL_CANCEL_QUEUE
);
514 ast_mutex_unlock(&p
->lock
);
515 /* Remove from list */
516 AST_LIST_LOCK(&locals
);
517 AST_LIST_REMOVE(&locals
, p
, list
);
518 AST_LIST_UNLOCK(&locals
);
519 /* Grab / release lock just in case */
520 ast_mutex_lock(&p
->lock
);
521 ast_mutex_unlock(&p
->lock
);
524 ast_mutex_destroy(&p
->lock
);
529 if (p
->chan
&& !ast_test_flag(p
, LOCAL_LAUNCHED_PBX
))
530 /* Need to actually hangup since there is no PBX */
533 res
= local_queue_frame(p
, isoutbound
, &f
, NULL
);
535 ast_mutex_unlock(&p
->lock
);
541 /*! \brief Create a call structure */
542 static struct local_pvt
*local_alloc(const char *data
, int format
)
544 struct local_pvt
*tmp
= NULL
;
545 char *c
= NULL
, *opts
= NULL
;
547 if (!(tmp
= ast_calloc(1, sizeof(*tmp
))))
550 /* Initialize private structure information */
551 ast_mutex_init(&tmp
->lock
);
552 ast_copy_string(tmp
->exten
, data
, sizeof(tmp
->exten
));
554 /* Look for options */
555 if ((opts
= strchr(tmp
->exten
, '/'))) {
557 if (strchr(opts
, 'n'))
558 ast_set_flag(tmp
, LOCAL_NO_OPTIMIZATION
);
561 /* Look for a context */
562 if ((c
= strchr(tmp
->exten
, '@')))
565 ast_copy_string(tmp
->context
, c
? c
: "default", sizeof(tmp
->context
));
567 tmp
->reqformat
= format
;
569 if (!ast_exists_extension(NULL
, tmp
->context
, tmp
->exten
, 1, NULL
)) {
570 ast_log(LOG_NOTICE
, "No such extension/context %s@%s creating local channel\n", tmp
->exten
, tmp
->context
);
571 ast_mutex_destroy(&tmp
->lock
);
576 AST_LIST_LOCK(&locals
);
577 AST_LIST_INSERT_HEAD(&locals
, tmp
, list
);
578 AST_LIST_UNLOCK(&locals
);
584 /*! \brief Start new local channel */
585 static struct ast_channel
*local_new(struct local_pvt
*p
, int state
)
587 struct ast_channel
*tmp
= NULL
, *tmp2
= NULL
;
588 int randnum
= ast_random() & 0xffff, fmt
= 0;
592 /* Allocate two new Asterisk channels */
593 /* safe accountcode */
594 if (p
->owner
&& p
->owner
->accountcode
)
595 t
= p
->owner
->accountcode
;
600 ama
= p
->owner
->amaflags
;
603 if (!(tmp
= ast_channel_alloc(1, state
, 0, 0, t
, p
->exten
, p
->context
, ama
, "Local/%s@%s-%04x,1", p
->exten
, p
->context
, randnum
))
604 || !(tmp2
= ast_channel_alloc(1, AST_STATE_RING
, 0, 0, t
, p
->exten
, p
->context
, ama
, "Local/%s@%s-%04x,2", p
->exten
, p
->context
, randnum
))) {
606 ast_channel_free(tmp
);
608 ast_channel_free(tmp2
);
609 ast_log(LOG_WARNING
, "Unable to allocate channel structure(s)\n");
613 tmp2
->tech
= tmp
->tech
= &local_tech
;
615 tmp
->nativeformats
= p
->reqformat
;
616 tmp2
->nativeformats
= p
->reqformat
;
618 /* Determine our read/write format and set it on each channel */
619 fmt
= ast_best_codec(p
->reqformat
);
620 tmp
->writeformat
= fmt
;
621 tmp2
->writeformat
= fmt
;
622 tmp
->rawwriteformat
= fmt
;
623 tmp2
->rawwriteformat
= fmt
;
624 tmp
->readformat
= fmt
;
625 tmp2
->readformat
= fmt
;
626 tmp
->rawreadformat
= fmt
;
627 tmp2
->rawreadformat
= fmt
;
634 p
->u_owner
= ast_module_user_add(p
->owner
);
635 p
->u_chan
= ast_module_user_add(p
->chan
);
637 ast_copy_string(tmp
->context
, p
->context
, sizeof(tmp
->context
));
638 ast_copy_string(tmp2
->context
, p
->context
, sizeof(tmp2
->context
));
639 ast_copy_string(tmp2
->exten
, p
->exten
, sizeof(tmp
->exten
));
647 /*! \brief Part of PBX interface */
648 static struct ast_channel
*local_request(const char *type
, int format
, void *data
, int *cause
)
650 struct local_pvt
*p
= NULL
;
651 struct ast_channel
*chan
= NULL
;
653 /* Allocate a new private structure and then Asterisk channel */
654 if ((p
= local_alloc(data
, format
)))
655 chan
= local_new(p
, AST_STATE_DOWN
);
660 /*! \brief CLI command "local show channels" */
661 static int locals_show(int fd
, int argc
, char **argv
)
663 struct local_pvt
*p
= NULL
;
666 return RESULT_SHOWUSAGE
;
668 AST_LIST_LOCK(&locals
);
669 if (!AST_LIST_EMPTY(&locals
)) {
670 AST_LIST_TRAVERSE(&locals
, p
, list
) {
671 ast_mutex_lock(&p
->lock
);
672 ast_cli(fd
, "%s -- %s@%s\n", p
->owner
? p
->owner
->name
: "<unowned>", p
->exten
, p
->context
);
673 ast_mutex_unlock(&p
->lock
);
676 ast_cli(fd
, "No local channels in use\n");
677 AST_LIST_UNLOCK(&locals
);
679 return RESULT_SUCCESS
;
682 static char show_locals_usage
[] =
683 "Usage: local show channels\n"
684 " Provides summary information on active local proxy channels.\n";
686 static struct ast_cli_entry cli_local
[] = {
687 { { "local", "show", "channels", NULL
},
688 locals_show
, "List status of local channels",
692 /*! \brief Load module into PBX, register channel */
693 static int load_module(void)
695 /* Make sure we can register our channel type */
696 if (ast_channel_register(&local_tech
)) {
697 ast_log(LOG_ERROR
, "Unable to register channel class 'Local'\n");
700 ast_cli_register_multiple(cli_local
, sizeof(cli_local
) / sizeof(struct ast_cli_entry
));
704 /*! \brief Unload the local proxy channel from Asterisk */
705 static int unload_module(void)
707 struct local_pvt
*p
= NULL
;
709 /* First, take us out of the channel loop */
710 ast_cli_unregister_multiple(cli_local
, sizeof(cli_local
) / sizeof(struct ast_cli_entry
));
711 ast_channel_unregister(&local_tech
);
712 if (!AST_LIST_LOCK(&locals
)) {
713 /* Hangup all interfaces if they have an owner */
714 AST_LIST_TRAVERSE(&locals
, p
, list
) {
716 ast_softhangup(p
->owner
, AST_SOFTHANGUP_APPUNLOAD
);
718 AST_LIST_UNLOCK(&locals
);
719 AST_LIST_HEAD_DESTROY(&locals
);
721 ast_log(LOG_WARNING
, "Unable to lock the monitor\n");
727 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Local Proxy Channel");