00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 #include "asterisk.h"
00033
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 407457 $")
00035
00036 #include <fcntl.h>
00037 #include <sys/signal.h>
00038
00039 #include "asterisk/lock.h"
00040 #include "asterisk/causes.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/config.h"
00043 #include "asterisk/module.h"
00044 #include "asterisk/pbx.h"
00045 #include "asterisk/sched.h"
00046 #include "asterisk/io.h"
00047 #include "asterisk/acl.h"
00048 #include "asterisk/callerid.h"
00049 #include "asterisk/file.h"
00050 #include "asterisk/cli.h"
00051 #include "asterisk/app.h"
00052 #include "asterisk/musiconhold.h"
00053 #include "asterisk/manager.h"
00054 #include "asterisk/stringfields.h"
00055 #include "asterisk/devicestate.h"
00056 #include "asterisk/astobj2.h"
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077 static const char tdesc[] = "Local Proxy Channel Driver";
00078
00079 #define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
00080
00081
00082
00083
00084 static const int BUCKET_SIZE = 1;
00085
00086 static struct ao2_container *locals;
00087
00088 static unsigned int name_sequence = 0;
00089
00090 static struct ast_jb_conf g_jb_conf = {
00091 .flags = 0,
00092 .max_size = -1,
00093 .resync_threshold = -1,
00094 .impl = "",
00095 .target_extra = -1,
00096 };
00097
00098 static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
00099 static int local_digit_begin(struct ast_channel *ast, char digit);
00100 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
00101 static int local_call(struct ast_channel *ast, const char *dest, int timeout);
00102 static int local_hangup(struct ast_channel *ast);
00103 static int local_answer(struct ast_channel *ast);
00104 static struct ast_frame *local_read(struct ast_channel *ast);
00105 static int local_write(struct ast_channel *ast, struct ast_frame *f);
00106 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
00107 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00108 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
00109 static int local_sendtext(struct ast_channel *ast, const char *text);
00110 static int local_devicestate(const char *data);
00111 static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
00112 static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
00113 static int local_setoption(struct ast_channel *chan, int option, void *data, int datalen);
00114
00115
00116 static struct ast_channel_tech local_tech = {
00117 .type = "Local",
00118 .description = tdesc,
00119 .requester = local_request,
00120 .send_digit_begin = local_digit_begin,
00121 .send_digit_end = local_digit_end,
00122 .call = local_call,
00123 .hangup = local_hangup,
00124 .answer = local_answer,
00125 .read = local_read,
00126 .write = local_write,
00127 .write_video = local_write,
00128 .exception = local_read,
00129 .indicate = local_indicate,
00130 .fixup = local_fixup,
00131 .send_html = local_sendhtml,
00132 .send_text = local_sendtext,
00133 .devicestate = local_devicestate,
00134 .bridged_channel = local_bridgedchannel,
00135 .queryoption = local_queryoption,
00136 .setoption = local_setoption,
00137 };
00138
00139
00140
00141
00142
00143
00144
00145
00146 struct local_pvt {
00147 unsigned int flags;
00148 char context[AST_MAX_CONTEXT];
00149 char exten[AST_MAX_EXTENSION];
00150 struct ast_format_cap *reqcap;
00151 struct ast_jb_conf jb_conf;
00152 struct ast_channel *owner;
00153 struct ast_channel *chan;
00154 };
00155
00156 #define LOCAL_ALREADY_MASQED (1 << 0)
00157 #define LOCAL_LAUNCHED_PBX (1 << 1)
00158 #define LOCAL_NO_OPTIMIZATION (1 << 2)
00159 #define LOCAL_BRIDGE (1 << 3)
00160 #define LOCAL_MOH_PASSTHRU (1 << 4)
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171 static void awesome_locking(struct local_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
00172 {
00173 struct ast_channel *chan = NULL;
00174 struct ast_channel *owner = NULL;
00175
00176 for (;;) {
00177 ao2_lock(p);
00178 if (p->chan) {
00179 chan = p->chan;
00180 ast_channel_ref(chan);
00181 }
00182 if (p->owner) {
00183 owner = p->owner;
00184 ast_channel_ref(owner);
00185 }
00186 ao2_unlock(p);
00187
00188
00189 if (!owner || !chan) {
00190 if (owner) {
00191 ast_channel_lock(owner);
00192 } else if(chan) {
00193 ast_channel_lock(chan);
00194 }
00195 ao2_lock(p);
00196 } else {
00197
00198 ast_channel_lock_both(chan, owner);
00199 ao2_lock(p);
00200 }
00201
00202
00203 if (p->owner != owner || p->chan != chan) {
00204 if (owner) {
00205 ast_channel_unlock(owner);
00206 owner = ast_channel_unref(owner);
00207 }
00208 if (chan) {
00209 ast_channel_unlock(chan);
00210 chan = ast_channel_unref(chan);
00211 }
00212 ao2_unlock(p);
00213 continue;
00214 }
00215
00216 break;
00217 }
00218 *outowner = p->owner;
00219 *outchan = p->chan;
00220 }
00221
00222
00223 static int local_setoption(struct ast_channel *ast, int option, void * data, int datalen)
00224 {
00225 int res = 0;
00226 struct local_pvt *p = NULL;
00227 struct ast_channel *otherchan = NULL;
00228 ast_chan_write_info_t *write_info;
00229
00230 if (option != AST_OPTION_CHANNEL_WRITE) {
00231 return -1;
00232 }
00233
00234 write_info = data;
00235
00236 if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
00237 ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
00238 return -1;
00239 }
00240
00241 if (!strcmp(write_info->function, "CHANNEL")
00242 && !strncasecmp(write_info->data, "hangup_handler_", 15)) {
00243
00244 return 0;
00245 }
00246
00247
00248 if (!(p = ast_channel_tech_pvt(ast))) {
00249 return -1;
00250 }
00251 ao2_ref(p, 1);
00252 ast_channel_unlock(ast);
00253
00254
00255 ao2_lock(p);
00256 otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
00257 if (!otherchan || otherchan == write_info->chan) {
00258 res = -1;
00259 otherchan = NULL;
00260 ao2_unlock(p);
00261 goto setoption_cleanup;
00262 }
00263 ast_channel_ref(otherchan);
00264
00265
00266 ao2_unlock(p);
00267
00268 ast_channel_lock(otherchan);
00269 res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
00270 ast_channel_unlock(otherchan);
00271
00272 setoption_cleanup:
00273 if (p) {
00274 ao2_ref(p, -1);
00275 }
00276 if (otherchan) {
00277 ast_channel_unref(otherchan);
00278 }
00279 ast_channel_lock(ast);
00280 return res;
00281 }
00282
00283
00284 static int local_devicestate(const char *data)
00285 {
00286 char *exten = ast_strdupa(data);
00287 char *context = NULL, *opts = NULL;
00288 int res;
00289 struct local_pvt *lp;
00290 struct ao2_iterator it;
00291
00292 if (!(context = strchr(exten, '@'))) {
00293 ast_log(LOG_WARNING, "Someone used Local/%s somewhere without a @context. This is bad.\n", exten);
00294 return AST_DEVICE_INVALID;
00295 }
00296
00297 *context++ = '\0';
00298
00299
00300 if ((opts = strchr(context, '/')))
00301 *opts = '\0';
00302
00303 ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
00304
00305 res = ast_exists_extension(NULL, context, exten, 1, NULL);
00306 if (!res)
00307 return AST_DEVICE_INVALID;
00308
00309 res = AST_DEVICE_NOT_INUSE;
00310
00311 it = ao2_iterator_init(locals, 0);
00312 for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
00313 int is_inuse;
00314
00315 ao2_lock(lp);
00316 is_inuse = !strcmp(exten, lp->exten)
00317 && !strcmp(context, lp->context)
00318 && lp->owner
00319 && ast_test_flag(lp, LOCAL_LAUNCHED_PBX);
00320 ao2_unlock(lp);
00321 if (is_inuse) {
00322 res = AST_DEVICE_INUSE;
00323 ao2_ref(lp, -1);
00324 break;
00325 }
00326 }
00327 ao2_iterator_destroy(&it);
00328
00329 return res;
00330 }
00331
00332
00333 static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
00334 {
00335 struct local_pvt *p = ast_channel_tech_pvt(bridge);
00336 struct ast_channel *bridged = bridge;
00337
00338 if (!p) {
00339 ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning <none>\n",
00340 ast_channel_name(chan), ast_channel_name(bridge));
00341 return NULL;
00342 }
00343
00344 ao2_lock(p);
00345
00346 if (ast_test_flag(p, LOCAL_BRIDGE)) {
00347
00348 bridged = (bridge == p->owner ? p->chan : p->owner);
00349
00350
00351 if (!bridged) {
00352 bridged = bridge;
00353 } else if (ast_channel_internal_bridged_channel(bridged)) {
00354 bridged = ast_channel_internal_bridged_channel(bridged);
00355 }
00356 }
00357
00358 ao2_unlock(p);
00359
00360 return bridged;
00361 }
00362
00363
00364 static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
00365 {
00366 struct local_pvt *p;
00367 struct ast_channel *bridged = NULL;
00368 struct ast_channel *tmp = NULL;
00369 int res = 0;
00370
00371 if (option != AST_OPTION_T38_STATE) {
00372
00373 return -1;
00374 }
00375
00376
00377 if (!(p = ast_channel_tech_pvt(ast))) {
00378 return -1;
00379 }
00380
00381 ao2_lock(p);
00382 if (!(tmp = IS_OUTBOUND(ast, p) ? p->owner : p->chan)) {
00383 ao2_unlock(p);
00384 return -1;
00385 }
00386 ast_channel_ref(tmp);
00387 ao2_unlock(p);
00388 ast_channel_unlock(ast);
00389
00390 ast_channel_lock(tmp);
00391 if (!(bridged = ast_bridged_channel(tmp))) {
00392 res = -1;
00393 ast_channel_unlock(tmp);
00394 goto query_cleanup;
00395 }
00396 ast_channel_ref(bridged);
00397 ast_channel_unlock(tmp);
00398
00399 query_cleanup:
00400 if (bridged) {
00401 res = ast_channel_queryoption(bridged, option, data, datalen, 0);
00402 bridged = ast_channel_unref(bridged);
00403 }
00404 if (tmp) {
00405 tmp = ast_channel_unref(tmp);
00406 }
00407 ast_channel_lock(ast);
00408
00409 return res;
00410 }
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420 static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f,
00421 struct ast_channel *us, int us_locked)
00422 {
00423 struct ast_channel *other = NULL;
00424
00425
00426 other = isoutbound ? p->owner : p->chan;
00427
00428 if (!other) {
00429 return 0;
00430 }
00431
00432
00433 if (us && ast_channel_generator(us) && ast_channel_generator(other)) {
00434 return 0;
00435 }
00436
00437
00438
00439 ast_channel_ref(other);
00440 if (us && us_locked) {
00441 ast_channel_unlock(us);
00442 }
00443 ao2_unlock(p);
00444
00445 if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
00446 ast_setstate(other, AST_STATE_RINGING);
00447 }
00448 ast_queue_frame(other, f);
00449
00450 other = ast_channel_unref(other);
00451 if (us && us_locked) {
00452 ast_channel_lock(us);
00453 }
00454 ao2_lock(p);
00455
00456 return 0;
00457 }
00458
00459 static int local_answer(struct ast_channel *ast)
00460 {
00461 struct local_pvt *p = ast_channel_tech_pvt(ast);
00462 int isoutbound;
00463 int res = -1;
00464
00465 if (!p) {
00466 return -1;
00467 }
00468
00469 ao2_lock(p);
00470 ao2_ref(p, 1);
00471 isoutbound = IS_OUTBOUND(ast, p);
00472 if (isoutbound) {
00473
00474 struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
00475 res = local_queue_frame(p, isoutbound, &answer, ast, 1);
00476 } else {
00477 ast_log(LOG_WARNING, "Huh? Local is being asked to answer?\n");
00478 }
00479 ao2_unlock(p);
00480 ao2_ref(p, -1);
00481 return res;
00482 }
00483
00484
00485
00486
00487
00488
00489
00490 static void check_bridge(struct ast_channel *ast, struct local_pvt *p)
00491 {
00492 struct ast_channel *owner;
00493 struct ast_channel *chan;
00494 struct ast_channel *bridged_chan;
00495 struct ast_frame *f;
00496
00497
00498 if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
00499 || !p->chan || !p->owner) {
00500 return;
00501 }
00502
00503
00504 chan = ast_channel_ref(p->chan);
00505
00506 ao2_unlock(p);
00507 bridged_chan = ast_bridged_channel(chan);
00508 ao2_lock(p);
00509
00510 chan = ast_channel_unref(chan);
00511
00512
00513
00514
00515 if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
00516 || !p->chan || !p->owner
00517 || (ast_channel_internal_bridged_channel(p->chan) != bridged_chan)) {
00518 return;
00519 }
00520
00521
00522
00523
00524
00525
00526 if (!ast_channel_internal_bridged_channel(p->chan)
00527 || !AST_LIST_EMPTY(ast_channel_readq(p->owner))
00528 || ast != p->chan ) {
00529 return;
00530 }
00531
00532
00533
00534
00535
00536 if (ast_channel_trylock(ast_channel_internal_bridged_channel(p->chan))) {
00537 return;
00538 }
00539 if (ast_check_hangup(ast_channel_internal_bridged_channel(p->chan))
00540 || ast_channel_trylock(p->owner)) {
00541 ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
00542 return;
00543 }
00544
00545
00546
00547
00548
00549
00550
00551
00552 f = AST_LIST_FIRST(ast_channel_readq(p->chan));
00553 if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
00554 AST_LIST_REMOVE_HEAD(ast_channel_readq(p->chan), frame_list);
00555 ast_frfree(f);
00556 f = AST_LIST_FIRST(ast_channel_readq(p->chan));
00557 }
00558
00559 if (f
00560 || ast_check_hangup(p->owner)
00561 || ast_channel_masquerade(p->owner, ast_channel_internal_bridged_channel(p->chan))) {
00562 ast_channel_unlock(p->owner);
00563 ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
00564 return;
00565 }
00566
00567
00568 ast_debug(4, "Masquerading %s <- %s\n",
00569 ast_channel_name(p->owner),
00570 ast_channel_name(ast_channel_internal_bridged_channel(p->chan)));
00571 if (ast_channel_monitor(p->owner)
00572 && !ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan))) {
00573 struct ast_channel_monitor *tmp;
00574
00575
00576
00577
00578
00579
00580 tmp = ast_channel_monitor(p->owner);
00581 ast_channel_monitor_set(p->owner, ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan)));
00582 ast_channel_monitor_set(ast_channel_internal_bridged_channel(p->chan), tmp);
00583 }
00584 if (ast_channel_audiohooks(p->chan)) {
00585 struct ast_audiohook_list *audiohooks_swapper;
00586
00587 audiohooks_swapper = ast_channel_audiohooks(p->chan);
00588 ast_channel_audiohooks_set(p->chan, ast_channel_audiohooks(p->owner));
00589 ast_channel_audiohooks_set(p->owner, audiohooks_swapper);
00590 }
00591
00592
00593
00594
00595
00596
00597
00598
00599 if (ast_channel_caller(p->owner)->id.name.valid || ast_channel_caller(p->owner)->id.number.valid
00600 || ast_channel_caller(p->owner)->id.subaddress.valid || ast_channel_caller(p->owner)->ani.name.valid
00601 || ast_channel_caller(p->owner)->ani.number.valid || ast_channel_caller(p->owner)->ani.subaddress.valid) {
00602 SWAP(*ast_channel_caller(p->owner), *ast_channel_caller(ast_channel_internal_bridged_channel(p->chan)));
00603 }
00604 if (ast_channel_redirecting(p->owner)->from.name.valid || ast_channel_redirecting(p->owner)->from.number.valid
00605 || ast_channel_redirecting(p->owner)->from.subaddress.valid || ast_channel_redirecting(p->owner)->to.name.valid
00606 || ast_channel_redirecting(p->owner)->to.number.valid || ast_channel_redirecting(p->owner)->to.subaddress.valid) {
00607 SWAP(*ast_channel_redirecting(p->owner), *ast_channel_redirecting(ast_channel_internal_bridged_channel(p->chan)));
00608 }
00609 if (ast_channel_dialed(p->owner)->number.str || ast_channel_dialed(p->owner)->subaddress.valid) {
00610 SWAP(*ast_channel_dialed(p->owner), *ast_channel_dialed(ast_channel_internal_bridged_channel(p->chan)));
00611 }
00612 ast_app_group_update(p->chan, p->owner);
00613 ast_set_flag(p, LOCAL_ALREADY_MASQED);
00614
00615 ast_channel_unlock(p->owner);
00616 ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
00617
00618
00619 owner = ast_channel_ref(p->owner);
00620 ao2_unlock(p);
00621 ast_channel_unlock(ast);
00622 ast_do_masquerade(owner);
00623 ast_channel_unref(owner);
00624 ast_channel_lock(ast);
00625 ao2_lock(p);
00626 }
00627
00628 static struct ast_frame *local_read(struct ast_channel *ast)
00629 {
00630 return &ast_null_frame;
00631 }
00632
00633 static int local_write(struct ast_channel *ast, struct ast_frame *f)
00634 {
00635 struct local_pvt *p = ast_channel_tech_pvt(ast);
00636 int res = -1;
00637 int isoutbound;
00638
00639 if (!p) {
00640 return -1;
00641 }
00642
00643
00644 ao2_ref(p, 1);
00645 ao2_lock(p);
00646 isoutbound = IS_OUTBOUND(ast, p);
00647
00648 if (isoutbound
00649 && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
00650 check_bridge(ast, p);
00651 }
00652
00653 if (!ast_test_flag(p, LOCAL_ALREADY_MASQED)) {
00654 res = local_queue_frame(p, isoutbound, f, ast, 1);
00655 } else {
00656 ast_debug(1, "Not posting to '%s' queue since already masqueraded out\n",
00657 ast_channel_name(ast));
00658 res = 0;
00659 }
00660 ao2_unlock(p);
00661 ao2_ref(p, -1);
00662
00663 return res;
00664 }
00665
00666 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00667 {
00668 struct local_pvt *p = ast_channel_tech_pvt(newchan);
00669
00670 if (!p) {
00671 return -1;
00672 }
00673
00674 ao2_lock(p);
00675
00676 if ((p->owner != oldchan) && (p->chan != oldchan)) {
00677 ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
00678 ao2_unlock(p);
00679 return -1;
00680 }
00681 if (p->owner == oldchan) {
00682 p->owner = newchan;
00683 } else {
00684 p->chan = newchan;
00685 }
00686
00687
00688 if (!ast_check_hangup(newchan) && ((p->owner && ast_channel_internal_bridged_channel(p->owner) == p->chan) || (p->chan && ast_channel_internal_bridged_channel(p->chan) == p->owner))) {
00689 ast_log(LOG_WARNING, "You can not bridge a Local channel to itself!\n");
00690 ao2_unlock(p);
00691 ast_queue_hangup(newchan);
00692 return -1;
00693 }
00694
00695 ao2_unlock(p);
00696 return 0;
00697 }
00698
00699 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
00700 {
00701 struct local_pvt *p = ast_channel_tech_pvt(ast);
00702 int res = 0;
00703 struct ast_frame f = { AST_FRAME_CONTROL, };
00704 int isoutbound;
00705
00706 if (!p) {
00707 return -1;
00708 }
00709
00710 ao2_ref(p, 1);
00711
00712
00713 if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_HOLD) {
00714 ast_moh_start(ast, data, NULL);
00715 } else if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_UNHOLD) {
00716 ast_moh_stop(ast);
00717 } else if (condition == AST_CONTROL_CONNECTED_LINE || condition == AST_CONTROL_REDIRECTING) {
00718 struct ast_channel *this_channel;
00719 struct ast_channel *the_other_channel;
00720
00721
00722
00723
00724
00725
00726
00727 ao2_lock(p);
00728 isoutbound = IS_OUTBOUND(ast, p);
00729 if (isoutbound) {
00730 this_channel = p->chan;
00731 the_other_channel = p->owner;
00732 } else {
00733 this_channel = p->owner;
00734 the_other_channel = p->chan;
00735 }
00736 if (the_other_channel) {
00737 unsigned char frame_data[1024];
00738 if (condition == AST_CONTROL_CONNECTED_LINE) {
00739 if (isoutbound) {
00740 ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel), ast_channel_connected(this_channel));
00741 }
00742 f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data), ast_channel_connected(this_channel), NULL);
00743 } else {
00744 f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data), ast_channel_redirecting(this_channel), NULL);
00745 }
00746 f.subclass.integer = condition;
00747 f.data.ptr = frame_data;
00748 res = local_queue_frame(p, isoutbound, &f, ast, 1);
00749 }
00750 ao2_unlock(p);
00751 } else {
00752
00753 ao2_lock(p);
00754
00755
00756
00757
00758
00759 if (0 <= condition || ast_test_flag(p, LOCAL_NO_OPTIMIZATION)) {
00760 isoutbound = IS_OUTBOUND(ast, p);
00761 f.subclass.integer = condition;
00762 f.data.ptr = (void *) data;
00763 f.datalen = datalen;
00764 res = local_queue_frame(p, isoutbound, &f, ast, 1);
00765
00766 if (!res && (condition == AST_CONTROL_T38_PARAMETERS) &&
00767 (datalen == sizeof(struct ast_control_t38_parameters))) {
00768 const struct ast_control_t38_parameters *parameters = data;
00769
00770 if (parameters->request_response == AST_T38_REQUEST_PARMS) {
00771 res = AST_T38_REQUEST_PARMS;
00772 }
00773 }
00774 } else {
00775 ast_debug(4, "Blocked indication %d\n", condition);
00776 }
00777 ao2_unlock(p);
00778 }
00779
00780 ao2_ref(p, -1);
00781 return res;
00782 }
00783
00784 static int local_digit_begin(struct ast_channel *ast, char digit)
00785 {
00786 struct local_pvt *p = ast_channel_tech_pvt(ast);
00787 int res = -1;
00788 struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
00789 int isoutbound;
00790
00791 if (!p) {
00792 return -1;
00793 }
00794
00795 ao2_ref(p, 1);
00796 ao2_lock(p);
00797 isoutbound = IS_OUTBOUND(ast, p);
00798 f.subclass.integer = digit;
00799 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00800 ao2_unlock(p);
00801 ao2_ref(p, -1);
00802
00803 return res;
00804 }
00805
00806 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
00807 {
00808 struct local_pvt *p = ast_channel_tech_pvt(ast);
00809 int res = -1;
00810 struct ast_frame f = { AST_FRAME_DTMF_END, };
00811 int isoutbound;
00812
00813 if (!p) {
00814 return -1;
00815 }
00816
00817 ao2_ref(p, 1);
00818 ao2_lock(p);
00819 isoutbound = IS_OUTBOUND(ast, p);
00820 f.subclass.integer = digit;
00821 f.len = duration;
00822 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00823 ao2_unlock(p);
00824 ao2_ref(p, -1);
00825
00826 return res;
00827 }
00828
00829 static int local_sendtext(struct ast_channel *ast, const char *text)
00830 {
00831 struct local_pvt *p = ast_channel_tech_pvt(ast);
00832 int res = -1;
00833 struct ast_frame f = { AST_FRAME_TEXT, };
00834 int isoutbound;
00835
00836 if (!p) {
00837 return -1;
00838 }
00839
00840 ao2_lock(p);
00841 ao2_ref(p, 1);
00842 isoutbound = IS_OUTBOUND(ast, p);
00843 f.data.ptr = (char *) text;
00844 f.datalen = strlen(text) + 1;
00845 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00846 ao2_unlock(p);
00847 ao2_ref(p, -1);
00848 return res;
00849 }
00850
00851 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
00852 {
00853 struct local_pvt *p = ast_channel_tech_pvt(ast);
00854 int res = -1;
00855 struct ast_frame f = { AST_FRAME_HTML, };
00856 int isoutbound;
00857
00858 if (!p) {
00859 return -1;
00860 }
00861
00862 ao2_lock(p);
00863 ao2_ref(p, 1);
00864 isoutbound = IS_OUTBOUND(ast, p);
00865 f.subclass.integer = subclass;
00866 f.data.ptr = (char *)data;
00867 f.datalen = datalen;
00868 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00869 ao2_unlock(p);
00870 ao2_ref(p, -1);
00871
00872 return res;
00873 }
00874
00875
00876
00877 static int local_call(struct ast_channel *ast, const char *dest, int timeout)
00878 {
00879 struct local_pvt *p = ast_channel_tech_pvt(ast);
00880 int pvt_locked = 0;
00881
00882 struct ast_channel *owner = NULL;
00883 struct ast_channel *chan = NULL;
00884 int res;
00885 struct ast_var_t *varptr;
00886 struct ast_var_t *clone_var;
00887 char *reduced_dest = ast_strdupa(dest);
00888 char *slash;
00889 const char *exten;
00890 const char *context;
00891
00892 if (!p) {
00893 return -1;
00894 }
00895
00896
00897
00898 ao2_ref(p, 1);
00899 ast_channel_unlock(ast);
00900
00901 awesome_locking(p, &chan, &owner);
00902 pvt_locked = 1;
00903
00904 if (owner != ast) {
00905 res = -1;
00906 goto return_cleanup;
00907 }
00908
00909 if (!owner || !chan) {
00910 res = -1;
00911 goto return_cleanup;
00912 }
00913
00914
00915
00916
00917
00918
00919
00920
00921 ast_party_redirecting_copy(ast_channel_redirecting(chan), ast_channel_redirecting(owner));
00922
00923 ast_party_dialed_copy(ast_channel_dialed(chan), ast_channel_dialed(owner));
00924
00925 ast_connected_line_copy_to_caller(ast_channel_caller(chan), ast_channel_connected(owner));
00926 ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(owner));
00927
00928 ast_channel_language_set(chan, ast_channel_language(owner));
00929 ast_channel_accountcode_set(chan, ast_channel_accountcode(owner));
00930 ast_channel_musicclass_set(chan, ast_channel_musicclass(owner));
00931 ast_cdr_update(chan);
00932
00933 ast_channel_cc_params_init(chan, ast_channel_get_cc_config_params(owner));
00934
00935
00936 if (ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
00937 ast_channel_hangupcause_set(chan, AST_CAUSE_ANSWERED_ELSEWHERE);
00938 }
00939
00940
00941
00942 AST_LIST_TRAVERSE(ast_channel_varshead(owner), varptr, entries) {
00943 clone_var = ast_var_assign(varptr->name, varptr->value);
00944 if (clone_var) {
00945 AST_LIST_INSERT_TAIL(ast_channel_varshead(chan), clone_var, entries);
00946 }
00947 }
00948 ast_channel_datastore_inherit(owner, chan);
00949
00950
00951
00952
00953 if ((slash = strrchr(reduced_dest, '/'))) {
00954 *slash = '\0';
00955 }
00956 ast_set_cc_interfaces_chanvar(chan, reduced_dest);
00957
00958 exten = ast_strdupa(ast_channel_exten(chan));
00959 context = ast_strdupa(ast_channel_context(chan));
00960
00961 ao2_unlock(p);
00962 pvt_locked = 0;
00963
00964 ast_channel_unlock(chan);
00965
00966 if (!ast_exists_extension(chan, context, exten, 1,
00967 S_COR(ast_channel_caller(owner)->id.number.valid, ast_channel_caller(owner)->id.number.str, NULL))) {
00968 ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", exten, context);
00969 res = -1;
00970 chan = ast_channel_unref(chan);
00971 goto return_cleanup;
00972 }
00973
00974
00975
00976
00977
00978
00979
00980
00981
00982
00983
00984
00985
00986
00987
00988
00989
00990
00991
00992
00993
00994
00995
00996
00997
00998
00999 manager_event(EVENT_FLAG_CALL, "LocalBridge",
01000 "Channel1: %s\r\n"
01001 "Channel2: %s\r\n"
01002 "Uniqueid1: %s\r\n"
01003 "Uniqueid2: %s\r\n"
01004 "Context: %s\r\n"
01005 "Exten: %s\r\n"
01006 "LocalOptimization: %s\r\n",
01007 ast_channel_name(p->owner), ast_channel_name(p->chan), ast_channel_uniqueid(p->owner), ast_channel_uniqueid(p->chan),
01008 p->context, p->exten,
01009 ast_test_flag(p, LOCAL_NO_OPTIMIZATION) ? "No" : "Yes");
01010
01011
01012
01013 if (!(res = ast_pbx_start(chan))) {
01014 ao2_lock(p);
01015 ast_set_flag(p, LOCAL_LAUNCHED_PBX);
01016 ao2_unlock(p);
01017 }
01018 chan = ast_channel_unref(chan);
01019
01020 return_cleanup:
01021 if (p) {
01022 if (pvt_locked) {
01023 ao2_unlock(p);
01024 }
01025 ao2_ref(p, -1);
01026 }
01027 if (chan) {
01028 ast_channel_unlock(chan);
01029 chan = ast_channel_unref(chan);
01030 }
01031
01032
01033
01034 if (owner) {
01035 if (owner != ast) {
01036 ast_channel_unlock(owner);
01037 ast_channel_lock(ast);
01038 }
01039 owner = ast_channel_unref(owner);
01040 } else {
01041
01042 ast_channel_lock(ast);
01043 }
01044
01045 return res;
01046 }
01047
01048
01049 static int local_hangup(struct ast_channel *ast)
01050 {
01051 struct local_pvt *p = ast_channel_tech_pvt(ast);
01052 int isoutbound;
01053 int hangup_chan = 0;
01054 int res = 0;
01055 struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_HANGUP }, .data.uint32 = ast_channel_hangupcause(ast) };
01056 struct ast_channel *owner = NULL;
01057 struct ast_channel *chan = NULL;
01058
01059 if (!p) {
01060 return -1;
01061 }
01062
01063
01064 ao2_ref(p, 1);
01065
01066
01067 ast_channel_unlock(ast);
01068
01069
01070 awesome_locking(p, &chan, &owner);
01071
01072 if (ast != chan && ast != owner) {
01073 res = -1;
01074 goto local_hangup_cleanup;
01075 }
01076
01077 isoutbound = IS_OUTBOUND(ast, p);
01078
01079 if (p->chan && ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
01080 ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
01081 ast_debug(2, "This local call has AST_CAUSE_ANSWERED_ELSEWHERE set.\n");
01082 }
01083
01084 if (isoutbound) {
01085 const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
01086
01087 if (status && p->owner) {
01088 ast_channel_hangupcause_set(p->owner, ast_channel_hangupcause(p->chan));
01089 pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
01090 }
01091
01092 ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
01093 p->chan = NULL;
01094 } else {
01095 if (p->chan) {
01096 ast_queue_hangup(p->chan);
01097 }
01098 p->owner = NULL;
01099 }
01100
01101 ast_channel_tech_pvt_set(ast, NULL);
01102
01103 if (!p->owner && !p->chan) {
01104 ao2_unlock(p);
01105
01106
01107 ao2_unlink(locals, p);
01108 ao2_ref(p, -1);
01109 p = NULL;
01110 res = 0;
01111 goto local_hangup_cleanup;
01112 }
01113 if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX)) {
01114
01115 hangup_chan = 1;
01116 } else {
01117 local_queue_frame(p, isoutbound, &f, NULL, 0);
01118 }
01119
01120 local_hangup_cleanup:
01121 if (p) {
01122 ao2_unlock(p);
01123 ao2_ref(p, -1);
01124 }
01125 if (owner) {
01126 ast_channel_unlock(owner);
01127 owner = ast_channel_unref(owner);
01128 }
01129 if (chan) {
01130 ast_channel_unlock(chan);
01131 if (hangup_chan) {
01132 ast_hangup(chan);
01133 }
01134 chan = ast_channel_unref(chan);
01135 }
01136
01137
01138 ast_channel_lock(ast);
01139 return res;
01140 }
01141
01142
01143
01144
01145
01146
01147
01148
01149
01150 static void local_pvt_destructor(void *vdoomed)
01151 {
01152 struct local_pvt *doomed = vdoomed;
01153
01154 doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
01155
01156 ast_module_unref(ast_module_info->self);
01157 }
01158
01159
01160 static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
01161 {
01162 struct local_pvt *tmp = NULL;
01163 char *c = NULL, *opts = NULL;
01164
01165 if (!(tmp = ao2_alloc(sizeof(*tmp), local_pvt_destructor))) {
01166 return NULL;
01167 }
01168 if (!(tmp->reqcap = ast_format_cap_dup(cap))) {
01169 ao2_ref(tmp, -1);
01170 return NULL;
01171 }
01172
01173 ast_module_ref(ast_module_info->self);
01174
01175
01176 ast_copy_string(tmp->exten, data, sizeof(tmp->exten));
01177
01178 memcpy(&tmp->jb_conf, &g_jb_conf, sizeof(tmp->jb_conf));
01179
01180
01181 if ((opts = strchr(tmp->exten, '/'))) {
01182 *opts++ = '\0';
01183 if (strchr(opts, 'n'))
01184 ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
01185 if (strchr(opts, 'j')) {
01186 if (ast_test_flag(tmp, LOCAL_NO_OPTIMIZATION))
01187 ast_set_flag(&tmp->jb_conf, AST_JB_ENABLED);
01188 else {
01189 ast_log(LOG_ERROR, "You must use the 'n' option for chan_local "
01190 "to use the 'j' option to enable the jitterbuffer\n");
01191 }
01192 }
01193 if (strchr(opts, 'b')) {
01194 ast_set_flag(tmp, LOCAL_BRIDGE);
01195 }
01196 if (strchr(opts, 'm')) {
01197 ast_set_flag(tmp, LOCAL_MOH_PASSTHRU);
01198 }
01199 }
01200
01201
01202 if ((c = strchr(tmp->exten, '@')))
01203 *c++ = '\0';
01204
01205 ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
01206 #if 0
01207
01208
01209
01210 if (!ast_exists_extension(NULL, tmp->context, tmp->exten, 1, NULL)) {
01211 ast_log(LOG_NOTICE, "No such extension/context %s@%s creating local channel\n", tmp->exten, tmp->context);
01212 tmp = local_pvt_destroy(tmp);
01213 } else {
01214 #endif
01215
01216 ao2_link(locals, tmp);
01217 #if 0
01218 }
01219 #endif
01220 return tmp;
01221 }
01222
01223
01224 static struct ast_channel *local_new(struct local_pvt *p, int state, const char *linkedid, struct ast_callid *callid)
01225 {
01226 struct ast_channel *tmp = NULL, *tmp2 = NULL;
01227 struct ast_format fmt;
01228 int generated_seqno = ast_atomic_fetchadd_int((int *)&name_sequence, +1);
01229 const char *t;
01230 int ama;
01231
01232
01233
01234 if (p->owner && ast_channel_accountcode(p->owner))
01235 t = ast_channel_accountcode(p->owner);
01236 else
01237 t = "";
01238
01239 if (p->owner)
01240 ama = ast_channel_amaflags(p->owner);
01241 else
01242 ama = 0;
01243
01244
01245
01246 if (!(tmp = ast_channel_alloc(1, state, 0, 0, t, p->exten, p->context, linkedid, ama, "Local/%s@%s-%08x;1", p->exten, p->context, generated_seqno))
01247 || !(tmp2 = ast_channel_alloc(1, AST_STATE_RING, 0, 0, t, p->exten, p->context, ast_channel_linkedid(tmp), ama, "Local/%s@%s-%08x;2", p->exten, p->context, generated_seqno))) {
01248 if (tmp) {
01249 tmp = ast_channel_release(tmp);
01250 }
01251 ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
01252 return NULL;
01253 }
01254
01255 if (callid) {
01256 ast_channel_callid_set(tmp, callid);
01257 ast_channel_callid_set(tmp2, callid);
01258 }
01259
01260 ast_channel_tech_set(tmp, &local_tech);
01261 ast_channel_tech_set(tmp2, &local_tech);
01262
01263 ast_format_cap_copy(ast_channel_nativeformats(tmp), p->reqcap);
01264 ast_format_cap_copy(ast_channel_nativeformats(tmp2), p->reqcap);
01265
01266
01267 ast_best_codec(p->reqcap, &fmt);
01268 ast_format_copy(ast_channel_writeformat(tmp), &fmt);
01269 ast_format_copy(ast_channel_writeformat(tmp2), &fmt);
01270 ast_format_copy(ast_channel_rawwriteformat(tmp), &fmt);
01271 ast_format_copy(ast_channel_rawwriteformat(tmp2), &fmt);
01272 ast_format_copy(ast_channel_readformat(tmp), &fmt);
01273 ast_format_copy(ast_channel_readformat(tmp2), &fmt);
01274 ast_format_copy(ast_channel_rawreadformat(tmp), &fmt);
01275 ast_format_copy(ast_channel_rawreadformat(tmp2), &fmt);
01276
01277 ast_channel_tech_pvt_set(tmp, p);
01278 ast_channel_tech_pvt_set(tmp2, p);
01279
01280 ast_set_flag(ast_channel_flags(tmp), AST_FLAG_DISABLE_DEVSTATE_CACHE);
01281 ast_set_flag(ast_channel_flags(tmp2), AST_FLAG_DISABLE_DEVSTATE_CACHE);
01282
01283 p->owner = tmp;
01284 p->chan = tmp2;
01285
01286 ast_channel_context_set(tmp, p->context);
01287 ast_channel_context_set(tmp2, p->context);
01288 ast_channel_exten_set(tmp2, p->exten);
01289 ast_channel_priority_set(tmp, 1);
01290 ast_channel_priority_set(tmp2, 1);
01291
01292 ast_jb_configure(tmp, &p->jb_conf);
01293
01294 return tmp;
01295 }
01296
01297
01298 static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
01299 {
01300 struct local_pvt *p;
01301 struct ast_channel *chan;
01302 struct ast_callid *callid = ast_read_threadstorage_callid();
01303
01304
01305 p = local_alloc(data, cap);
01306 if (!p) {
01307 chan = NULL;
01308 goto local_request_end;
01309 }
01310 chan = local_new(p, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL, callid);
01311 if (!chan) {
01312 ao2_unlink(locals, p);
01313 } else if (ast_channel_cc_params_init(chan, requestor ? ast_channel_get_cc_config_params((struct ast_channel *)requestor) : NULL)) {
01314 ao2_unlink(locals, p);
01315 p->owner = ast_channel_release(p->owner);
01316 p->chan = ast_channel_release(p->chan);
01317 chan = NULL;
01318 }
01319 ao2_ref(p, -1);
01320
01321 local_request_end:
01322
01323 if (callid) {
01324 ast_callid_unref(callid);
01325 }
01326
01327 return chan;
01328 }
01329
01330
01331 static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01332 {
01333 struct local_pvt *p = NULL;
01334 struct ao2_iterator it;
01335
01336 switch (cmd) {
01337 case CLI_INIT:
01338 e->command = "local show channels";
01339 e->usage =
01340 "Usage: local show channels\n"
01341 " Provides summary information on active local proxy channels.\n";
01342 return NULL;
01343 case CLI_GENERATE:
01344 return NULL;
01345 }
01346
01347 if (a->argc != 3) {
01348 return CLI_SHOWUSAGE;
01349 }
01350
01351 if (ao2_container_count(locals) == 0) {
01352 ast_cli(a->fd, "No local channels in use\n");
01353 return RESULT_SUCCESS;
01354 }
01355
01356 it = ao2_iterator_init(locals, 0);
01357 while ((p = ao2_iterator_next(&it))) {
01358 ao2_lock(p);
01359 ast_cli(a->fd, "%s -- %s@%s\n", p->owner ? ast_channel_name(p->owner) : "<unowned>", p->exten, p->context);
01360 ao2_unlock(p);
01361 ao2_ref(p, -1);
01362 }
01363 ao2_iterator_destroy(&it);
01364
01365 return CLI_SUCCESS;
01366 }
01367
01368 static struct ast_cli_entry cli_local[] = {
01369 AST_CLI_DEFINE(locals_show, "List status of local channels"),
01370 };
01371
01372 static int manager_optimize_away(struct mansession *s, const struct message *m)
01373 {
01374 const char *channel;
01375 struct local_pvt *p, *tmp = NULL;
01376 struct ast_channel *c;
01377 int found = 0;
01378 struct ao2_iterator it;
01379
01380 channel = astman_get_header(m, "Channel");
01381
01382 if (ast_strlen_zero(channel)) {
01383 astman_send_error(s, m, "'Channel' not specified.");
01384 return 0;
01385 }
01386
01387 c = ast_channel_get_by_name(channel);
01388 if (!c) {
01389 astman_send_error(s, m, "Channel does not exist.");
01390 return 0;
01391 }
01392
01393 p = ast_channel_tech_pvt(c);
01394 ast_channel_unref(c);
01395 c = NULL;
01396
01397 it = ao2_iterator_init(locals, 0);
01398 while ((tmp = ao2_iterator_next(&it))) {
01399 if (tmp == p) {
01400 ao2_lock(tmp);
01401 found = 1;
01402 ast_clear_flag(tmp, LOCAL_NO_OPTIMIZATION);
01403 ao2_unlock(tmp);
01404 ao2_ref(tmp, -1);
01405 break;
01406 }
01407 ao2_ref(tmp, -1);
01408 }
01409 ao2_iterator_destroy(&it);
01410
01411 if (found) {
01412 astman_send_ack(s, m, "Queued channel to be optimized away");
01413 } else {
01414 astman_send_error(s, m, "Unable to find channel");
01415 }
01416
01417 return 0;
01418 }
01419
01420
01421 static int locals_cmp_cb(void *obj, void *arg, int flags)
01422 {
01423 return (obj == arg) ? CMP_MATCH : 0;
01424 }
01425
01426
01427 static int load_module(void)
01428 {
01429 if (!(local_tech.capabilities = ast_format_cap_alloc())) {
01430 return AST_MODULE_LOAD_FAILURE;
01431 }
01432 ast_format_cap_add_all(local_tech.capabilities);
01433
01434 if (!(locals = ao2_container_alloc(BUCKET_SIZE, NULL, locals_cmp_cb))) {
01435 ast_format_cap_destroy(local_tech.capabilities);
01436 return AST_MODULE_LOAD_FAILURE;
01437 }
01438
01439
01440 if (ast_channel_register(&local_tech)) {
01441 ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
01442 ao2_ref(locals, -1);
01443 ast_format_cap_destroy(local_tech.capabilities);
01444 return AST_MODULE_LOAD_FAILURE;
01445 }
01446 ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
01447 ast_manager_register_xml("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
01448
01449 return AST_MODULE_LOAD_SUCCESS;
01450 }
01451
01452
01453 static int unload_module(void)
01454 {
01455 struct local_pvt *p = NULL;
01456 struct ao2_iterator it;
01457
01458
01459 ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
01460 ast_manager_unregister("LocalOptimizeAway");
01461 ast_channel_unregister(&local_tech);
01462
01463 it = ao2_iterator_init(locals, 0);
01464 while ((p = ao2_iterator_next(&it))) {
01465 if (p->owner) {
01466 ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
01467 }
01468 ao2_ref(p, -1);
01469 }
01470 ao2_iterator_destroy(&it);
01471 ao2_ref(locals, -1);
01472
01473 ast_format_cap_destroy(local_tech.capabilities);
01474 return 0;
01475 }
01476
01477 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Local Proxy Channel (Note: used internally by other modules)",
01478 .load = load_module,
01479 .unload = unload_module,
01480 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
01481 );