· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
Asterisk Source/Mainto Dial

Overview

* Asterisk Internal Architecture Overview Created by: knielsen
This page tries to present an overview of the Asterisk core.

The information here is based on my study of the Asterisk source at a point (May 2005) where I was a relative newcomer to Asterisk, and needed this information in order to program a new channel driver. Corrections and additions welcome!
  • Channels
Call processing in Asterisk is centered around channel drivers. Many channel drivers are included with Asterisk in the channels/ subdirectory; other channel drivers are available separately. Channel drivers handle all the protocol-specific details of ISDN, SIP, and other telephony protocols and interface them to Asterisk.
Some channel drivers (like chan_local and chan_agent) work as “proxy channels” and do not directly interface to real protocols or hardware.

A channel driver is a shared object (.so) file that is loaded dynamically into Asterisk as a module. Modules are usually loaded automatically from modules.conf, but may also be loaded/unloaded explicitly from the CLI with the load and unload commands. Use show channeltypes to list all loaded channel drivers.

A channel driver must export the functions load_module(), unload_module(), usecount(), description(), and key(). Since Asterisk applications are also modules, the example skeleton application in apps/app_skel.c may be useful to get started with channel drivers.

The main task of the module part of the channel driver is to have the load_module() entry point call ast_channel_register() with a pointer to its struct ast_channel_tech. This is what makes the channel driver available in Asterisk.
  • Channel Technology Descriptor
The struct ast_channel_tech is the channel technology descriptor and defines the complete behaviour of the channel driver. It includes

    * The type name used to reference the channel (eg. `SIP’); this is used as the part before the slash in Dial(SIP/xxx).
    * Textual description.
    * Set of audio formats supported (ULAW, GSM, …).
    * Callback functions called by Asterisk to initiate and manage calls to and from the channel.

The requester callback is used to reserve a single channel from the driver (some channels like an E1 PRI interface has a limited number of channels available). It calls ast_channel_alloc() to allocate a new struct ast_channel and returns it. It also fills in the tech_pvt field with a pointer to a channel private structure that holds driver-specific data. All other operations on the channel reference the struct ast_channel. The requester callback does not block waiting for I/O. Called by ast_request().

The call callback initiates outgoing calls on the channel. It may block on I/O waiting to get the call established, but it does not wait for the remote end to answer (that is indicated by returning an AST_CONTROL_ANSWER control frame from the read callback). Called by ast_call().

The main call processing happens in the read and write callbacks. A channel does not have a specific OS thread associated with it (though a driver is free to create one for it if necessary). Instead the driver registers one or more file descriptors in the fds field of struct ast_channel. When data becomes available on any of the file descriptors, the read callback is called (via ast_read() to read the data and return an appropriate frame. This includes incoming audio data, but also control frames indicating things like remote answer or hangup; see frame.h for possible frame types. The driver may also inject frames outside of the read callback using ast_queue_frame() or ast_queue_control() (this may require calling ast_channel_alloc(1) to reserve an “alert pipe” if not using zaptel(?)). The write callback is called periodically by Asterisk (via ast_write()) to pass outgoing voice frames to the channel.
  • Getting things moving
The is no central “main loop” or kernel thread in Asterisk. Instead, processing is distributed among a number of threads, mainly of two kinds:

    * Dialplan threads, started by ast_pbx_start() to run a single instance/session of the dial plan. Such a thread may control multiple active channels at once, for example when forwarding a call with the Dial or Queue applications.
    * Channel driver monitor threads. Most channel drivers have a single monitor thread that listens for incoming calls. When a call arrives it is passed to ast_pbx_start() to start executing the dialplan.

When a thread is in control of one or more active channels, it must continously call ast_read() and ast_write() on these channels to keep the voice and control frames flowing. Again, there is no central loop in the code where this happens, instead custom read/write loops are used in each instance as appropriate. For example wait_for_answer() in the Dial application, ast_generic_bridge in channel.c to connect two channels during a conversation, and ast_waitstream() in file.c. The ast_waitfor*() family of functions are used in these loops to wait until input is available on a channel, at which point ast_read() is called on that channel (maybe passing the data on to ast_write() on another channel).
  • Conclusion
The overall flow of control in Asterisk is as follows:

    * Channel drivers monitor threads listen for incoming calls and starts dialplan threads for each new call with ast_pbx_start().
    * Dialplan threads run applications as specified by the dialplan.
    * Application commands process voice and control frames in processing loops alternating calls to ast_waitfor*() and ast_read()/ast_write(); and initiate new outgoing calls with ast_request() and ast_call().

main 0.1.0


* int main(int argc, char *argv[])
  • if (geteuid()) {
    • Must be run as root, exit(1);
  • }
  • while((c=getopt(argc, argv, "dvq")) != EOF) {
    • case d, v, q
    • exit(1);
  • }
  • signal 처리
  • if (init_logger()) event_log 화일을 append mode 로 open 한다.
    • exit(1);
  • if (load_pbx()) builtin function 을 ast_app 에 등록한다.
    • exit(1);
  • if (load_modules()) module 들의 load_module 을 수행하고, module_list 에 등록한다.
    • exit(1);
    • module 이 application 이면, application list 에 link 시키고, channel 이면 channel list 에 link 시킨다.
  • select(0,NULL,NULL,NULL,NULL);
    • interrupt 가 들어 올 때까지 대기상태로 있는다.
  • return 0;
If the readfs , writefs , and errorfds arguments are all null pointers and the timeout  argument is a null pointer, select()  blocks until interrupted by a signal.
model.txt
Description of call model:

Incoming Call:

        Channel backend waits for a RING or equivalent on some sort of
interface. Typically this is done in its own thread.  When a RING is
detected, the backend should create a channel structure and then call
ast_pbx_start() on that channel, which will create a thread to monitor
that interface.  At this point, the PBX and/or applications it launches
will manage the interface, and it need not be montored by the
aforementioned thread.  When the applications are finished, the requisite
hangup function will be called, at which the channel can be considered to
be no longer valid, and the thread that controls it will immenantly be
terminated.

* include/logger.h
  • #define EVENTLOG "event_log"

* logger.c
  • #define AST_EVENT_LOG AST_LOG_DIR "/" EVENTLOG
  • AST_EVENT_LOG: /var/log/asterisk/event_log
* int init_logger(void)
  • mkdir(AST_LOG_DIR, 0755);
  • eventlog = fopen(AST_EVENT_LOG, "a");
  • if (eventlog) {
    • return 0;
  • } else
  • return -1;

* pbx.c
struct ast_app {
        /* Name of the application */
        char name[AST_MAX_APP];
        int (*execute)(struct ast_channel *chan, void *data);
        struct ast_app *next;
};

static struct ast_app *apps = NULL;

static struct pbx_builtin {
        char name[AST_MAX_APP];
        int (*execute)(struct ast_channel *chan, void *data);
} builtins[] =
{
        /* These applications are built into the PBX core and do not
           need separate modules */
        { "Answer", pbx_builtin_answer },
        { "Goto", pbx_builtin_goto },
        { "Hangup", pbx_builtin_hangup },
        { "DigitTimeout", pbx_builtin_dtimeout },
        { "ResponseTimeout", pbx_builtin_rtimeout },
        { "BackGround", pbx_builtin_background },
        { "Wait", pbx_builtin_wait },
};
* int load_pbx(void)
  • for (x=0;x
    • if (ast_register_application(builtinsx.name, builtinsx.execute)) {
      • return -1;
    • }
  • }
  • return 0;

* int ast_register_application(char *app, int (*execute)(struct ast_channel *, void *))
  • application list 에 lock 을 걸수 없으면 return -1;
  • tmp = apps;
  • while(tmp) {
    • 이미 등록되어 있으면 return -1;
  • }
  • tmp = malloc(sizeof(struct ast_app));
  • if (tmp) {
    • strncpy(tmp->name, app, sizeof(tmp->name));
    • tmp->execute = execute;
  • } else {
    • return -1;
  • }
  • return 0;

* int load_modules()
  • module.h:#define AST_MODULE_CONFIG "modules.conf"
  • cfg = ast_load(AST_MODULE_CONFIG);
  • if (cfg) {
    • modules.conf 파일이 있으면
    • v = ast_variable_browse(cfg, "modules");
    • while(v) {
      • modules category 가 있으면
      • if (!strcasecmp(v->name, "load")) {
        • load 가 있으면
        • if (ast_load_resource(v->value)) {
          • return -1;
  • if (!cfg || ast_true(ast_variable_retrieve(cfg, "modules", "autoload"))) {
    • modules.conf 파일이 없거나, modules category 에 autoload 가 있으면
    • mods = opendir(AST_MODULE_DIR);
    • if (mods) {
      • while((d = readdir(mods))) {
        • .so file 이고 module list 에 없으면 {
          • cfg 에 noload 면 skip
          • if (ast_load_resource(d->d_name)) {
            • return -1;
          • }
        • }
      • }
    • } else {
    • }
  • }
  • ast_destroy(cfg);
  • return 0;
    • }
    • }
    • v=v->next;
    • }
  • }

struct module {
        int (*load_module)(void);
        int (*unload_module)(void);
        int (*usecount)(void);
        char *(*description)(void);
        void *lib;
        char resource[256];
        struct module *next;
};

static struct module *module_list=NULL;
* int ast_load_resource(char *resource_name)
  • struct module *m = malloc(sizeof(struct module));
  • strncpy(m->resource, resource_name, sizeof(m->resource));
  • m->lib = dlopen(fn, RTLD_NOW | RTLD_GLOBAL);
  • m->load_module = dlsym(m->lib, "load_module");
  • m->unload_module = dlsym(m->lib, "unload_module");
  • m->usecount = dlsym(m->lib, "usecount");
  • m->description = dlsym(m->lib, "description");
  • if ((res = m->load_module())) {
    • return -1;
    • channel driver 인 경우 load 되고, wait 상태에 있게 된다.
  • }
  • m->next = module_list;
  • module_list = m;
  • ast_update_use_count();
  • return 0;

* dlsym - obtain the address of a symbol from a dlopen object

ast_load 0.1.0

* config.c

struct ast_variable {
        char *name;
        char *value;
        struct ast_variable *next;
};

struct ast_category {
        char name[80];
        struct ast_variable *root;
        struct ast_category *next;
};

struct ast_config {
        /* Maybe this structure isn't necessary but we'll keep it
           for now */
        struct ast_category *root;
};

* struct ast_config *ast_load(char *configfile)
  • asterisk.h:#define AST_CONFIG_DIR "/etc/asterisk"
  • fn = AST_CONFIG_DIR + '/' + configfile
  • f = fopen(fn, "r")
    • tmp = malloc(sizeof(struct ast_config));
    • tmp->root = NULL;
    • while(!feof(f))
      • fgets(buf, sizeof(buf), f);
        • category
          • tmpc = malloc(sizeof(struct ast_category));
          • strncpy(tmpc->name, cur+1, sizeof(tmpc->name));
        • variable = value
          • v = malloc(sizeof(struct ast_variable));
          • v->name = strdup(strip(cur));
          • v->value = strdup(strip(c));
          • category struct 와 연결
    • fclose(f);
  • return tmp;

* char *ast_category_browse(struct ast_config *config, char *prev)
  • prev 가 NULL 이면, 첫번째 category 명을 return
  • prev category 가 존재하면, 다음 category 명을 return

* struct ast_variable *ast_variable_browse(struct ast_config *config, char *category)
  • category 가 존재하면, 첫번째 variable 의 pointer return

* char *ast_variable_retrieve(struct ast_config *config, char *category, char *value)
  • category 존재하고, variable 존재하면, 해당 값을 return

chan_ixj.c

Connected to the PhoneJack PCI card is a Nortel Venture phone. My
phone.conf file has "mode=dialtone", "format=slinear", "echocancel=off",
"txgain=100%", "rxgain=1.0", and "device => /dev/phone0"

* load_module()
  • static char *config = "ixj.conf";
  • cfg = ast_load(config);
  • v = ast_variable_browse(cfg, "interfaces");
  • while(v)
    • strcasecmp(v->name, "device")
      • tmp = mkif(v->value, mode);
      • tmp->next = iflist;
      • iflist = tmp;
      • 각각의 device 는 interface 에 해당되고 이것들은 각각의 channel 에 해당된다. iflist 를 이용하여 do_monitor 에서 각 interface 의 input 을 check 한다.
    • strcasecmp(v->name, "mode")
      • mode = MODE_DIALTONE or MODE_IMMEDIATE
    • strcasecmp(v->name, "context"
      • strncpy(context, v->value, sizeof(context));
      • context 값이 없으면, default 를 사용함.
  • ast_channel_register(type, tdesc, AST_FORMAT_G723_1, ixj_request)
  • restart_monitor();

* struct ixj_pvt *mkif(char *iface, int mode)
  • tmp->fd = open(iface, O_RDWR);
    • iface 는 /dec/phone0
  • device 를 open 하고, 관련 설정을 한다.
  • tmp->owner = NULL;
  • tmp->dialtone = 0;

ast_channel_register

struct ast_channel {
        char name[AST_CHANNEL_NAME];            /* ASCII Description of channel name */
        pthread_t blocker;                                      /* If anyone is blocking, this is them */
        char *blockproc;                                        /* Procedure causing blocking */
        int blocking;                                           /* Whether or not we're blocking */
        struct sched_context *sched;            /* Schedule context */
        int streamid;                                   /* For streaming playback, the schedule ID */
        struct ast_filestream *stream;  /* Stream itself. */
        struct ast_channel *trans;              /* Translator if present */
        struct ast_channel *master;             /* Master channel, if this is a translator */
        int fd;                                 /* File descriptor for channel -- all must have
                                                   a file descriptor! */
        char *type;                             /* Type of channel */
        int state;                              /* State of line */
        int rings;                              /* Number of rings so far */
        int stack;                              /* Current level of application */
        int format;                             /* Kinds of data this channel can
                                                           natively handle */
        char *dnid;                             /* Malloc'd Dialed Number Identifier */
        char *callerid;                 /* Malloc'd Caller ID */
        char context[AST_MAX_EXTENSION];        /* Current extension context */
        char exten[AST_MAX_EXTENSION];          /* Current extension number */
        int priority;                                           /* Current extension priority */
        void *app[AST_CHANNEL_MAX_STACK];       /* Application information -- see assigned numbers */
        struct ast_channel_pvt *pvt;
                                                /* Private channel implementation details */
        jmp_buf jmp[AST_CHANNEL_MAX_STACK];             /* Jump buffer used for returning from applications */
        struct ast_pbx *pbx;
        struct ast_channel *next;               /* For easy linking */
};
struct chanlist {
        char type[80];
        char description[80];
        int capabilities;
        struct ast_channel * (*requester)(char *type, int format, void *data);
        struct chanlist *next;
} *backends = NULL;

* int ast_channel_register(char *type, char *description, int capabilities, struct ast_channel *(*requester)(char *type, int format, void *data))
  • chanlist 는 channel list 가 아니라 channel driver 의 list 임.
  • 실제 channel 즉 ast_channel 은 requester 에 의해 얻는다.
  • channel list 에 lock 을 걸수 없으면 return -1;
  • chan = backends;
  • while(chan) {
    • 이미 등록되어 있으면 return -1;
  • }
  • chan = malloc(sizeof(struct chanlist));
  • channel structure 에 type, description, capabilities, requester 을 설정
  • chan->next = NULL;
  • channel list 끝에 link
  • return 0;

* chanlist struct 의 requester 에 각 모듈의 request 함수를 assign 한다. 이는 ast_request 함수에 의해 수행된다.

* static int dial_exec(struct ast_channel *chan, void *data)
  • app_dial.c
  • tmp->chan = ast_request(tech, chan->format, number);

* struct ast_channel *ast_request(char *type, int format, void *data)
  • channel.c
  • c = chan->requester(type, format, data);

* static struct ast_channel *ixj_request(char *type, int format, void *data)
  • tmp = ixj_new(p, AST_STATE_DOWN);

restart_monitor

  • monitor_thread == pthread_self()
  • thread 가 있으면, thread 를 없앤다.
    • pthread_cancel(monitor_thread); or pthread_join(monitor_thread, NULL);
  • pthread_create(&monitor_thread, NULL, do_monitor, NULL)

* do_monitor
  • for
    • FD_ZERO(&rfds);
    • FD_ZERO(&efds);
    • i = iflist;
    • dotone = 0;
    • while(i)
      • if (!i->owner)
        • FD_SET(i->fd, &rfds);
        • FD_SET(i->fd, &efds);
        • if (i->dialtone)
          • if (write(i->fd, DialTone + tonepos, 240) != 240)
          • dotone++;
      • i = i->next;
    • if (dotone)
      • res = select(n + 1, &rfds, NULL, &efds, &tv);
      • 입력을 tv 시간동안 기다린후 return. 다시 loop 로 돌아가 DialTone 을 write 한다.
    • else
      • res = select(n + 1, &rfds, NULL, &efds, NULL);
      • 입력을 무한정 기다린다.
    • i = iflist;
    • while(i)
      • FD_ISSET(i->fd, &rfds)
        • ixj_mini_packet(i);
      • FD_ISSET(i->fd, &efds)
        • ixj_check_exception(i);
      • i=i->next;
read, write, exception, time
성공시, select는 파일지정자집합에 표함된 숫자를 반환하며, 리턴하기전에 타임아웃이 발생하면 0을 반환한다. 이러시 -1이 반환되며, errno는 적당한 값으로 설정된다.

* static void ixj_mini_packet(struct ixj_pvt *i)
  • res = read(i->fd, buf, sizeof(buf));

ixj_check_exception

typedef struct
{
  unsigned int dtmf_ready:1;
  unsigned int hookstate:1;
  unsigned int pstn_ring:1;
  unsigned int caller_id:1;
  unsigned int pstn_wink:1;
  unsigned int f0:1;
  unsigned int f1:1;
  unsigned int f2:1;
  unsigned int f3:1;
  unsigned int reserved:23;
}IXJ_EXCEPT;

typedef union
{
  IXJ_EXCEPT bits;
  unsigned int bytes;
}IXJ_EXCEPTION;

The dtmf_ready bit indicates if there is data waiting in the DTMF buffer.  The hookstate bit is set if there is a change in hookstate status, it does not indicate the current state of the hookswitch.  
The pstn_ring bit indicates that the DAA on a LineJACK card has detected ring voltage on the PSTN port.  
The caller_id bit indicates that caller_id data has been received and is available.  
The pstn_wink bit indicates that the DAA on the LineJACK has received a wink from the telco switch

* static void ixj_check_exception(struct ixj_pvt *i)
  • ixje.bytes = ioctl(i->fd, IXJCTL_EXCEPTION);
  • if (ixje.bits.dtmf_ready) 숫자가 눌려지면
    • digit0 = ioctl(i->fd, IXJCTL_GET_DTMF_ASCII); 입력된 숫자를 가져온다.
    • if (i->mode == MODE_DIALTONE)
      • ioctl IXJCTL_PLAY_STOP, IXJCTL_REC_STOP, IXJCTL_CPT_STOP
      • i->dialtone = 0;
      • if (strlen(i->ext) < AST_MAX_EXTENSION - 1)
        • strcat(i->ext, digit);
        • extension 에 해당하는 만큼의 숫자가 들어 오지 않았으면, i->ext 에 append 한다.
      • extension 에 해당하는 만큼의 숫자가 들어 왔으면
      • if (ast_exists_extension(NULL, i->context, i->ext, 1)) {
        • i->context 에서 해당하는 extension이 있으면
        • ixj_new(i, AST_STATE_UP);
        • /* No need to restart monitor, we are the monitor */
        • if (i->owner)
          • ixj_setup(i->owner);
      • } else if (ast_exists_extension(NULL, "default", i->ext, 1)) {
        • default context 에서 extension이 있으면
        • 위와 똑같은 일을 수행한다.
      • } else if ((strlen(i->ext) >= ast_pbx_longest_extension(i->context)) & (strlen(i->ext) >= ast_pbx_longest_extension("default"))) {
        • It's not a valid extension, give a busy signal
        • ioctl(i->fd, IXJCTL_BUSY);
      • }
    • }
  • if (ixje.bits.hookstate) hook state 가 변하면
    • offhook = ioctl(i->fd, IXJCTL_HOOKSTATE);
    • if (offhook) 수화기를 들면
      • ioctl(i->fd, IXJCTL_DIALTONE);
      • offhook 을 하면 dialtone 을 내 보내고, 다음 exception 을 기다린다.
    • else 수화기를 내려놓으면
      • memset(i->ext, 0, sizeof(i->ext));
      • ioctl(i->fd, IXJCTL_CPT_STOP);
      • ioctl(i->fd, IXJCTL_PLAY_STOP);
      • ioctl(i->fd, IXJCTL_REC_STOP);
      • i->dialtone = 0;

  • if (ixje.bits.pstn_ring)
    • ast_verbose("Unit is ringing\n");
  • if (ixje.bits.caller_id)
    • ast_verbose("We have caller ID\n");

#define HELPER_EXISTS 0
#define HELPER_SPAWN 1
#define HELPER_EXEC 2
* int ast_exists_extension(struct ast_channel *c, char *context, char *exten, int priority)
  • return pbx_extension_helper(c, context, exten, priority, HELPER_EXISTS);

* static int pbx_extension_helper(struct ast_channel *c, char *context, char *exten, int priority, int action)
  • context, extension, priority 가 같은 extension 을 찾는다
  • action 이 HELP_EXISTS 면 return -1;
  • action 이 HELPER_SPAWN 이면 newstack++;
  • action 이 HELPER_EXEC 이면
    • app = pbx_findapp(e->app);
    • return pbx_exec(c, app->execute, e->data, newstack);
  • 없고, action 이 HELP_EXISTS 가 아니면 return -1;
  • 없고, action 이 HELP_EXISTS 면 return 0;

* static struct ast_channel *ixj_new(struct ixj_pvt *i, int state)
  • tmp = ast_channel_alloc();
  • set ast_channel structure
    • tmp 에 type, i->fd, AST_FORMAT_G723_1, state 를 assign
    • if (state == AST_STATE_RING)
      • tmp->rings = 1;
    • tmp->pvt->pvt = i; i->ext 는 extension number
    • tmp->pvt 에 ixj_digit, ixj_call, ixj_hangup, ixj_answer, ixj_read, ixj_write 를 assign
    • tmp 에 context, extension 을 복사
  • i->owner = tmp;
  • ast_update_use_count();
  • if (state != AST_STATE_DOWN)
    • if (state == AST_STATE_RING)
      • ioctl(tmp->fd, IXJCTL_RINGBACK);
    • if (ast_pbx_start(tmp))
      • ast_hangup(tmp);
  • return tmp;


ast_pbx_start

* int ast_pbx_start(struct ast_channel *c)
  • if (pthread_create(&t, &attr, pbx_thread, c))
    • return -1
  • return 0

* static void *pbx_thread(void *data)
  • struct ast_channel *c = data;
  • if (!ast_exists_extension(c, c->context, c->exten, c->priority, c->callerid)) {
    • strncpy(c->context, "default", sizeof(c->context)-1);
    • strncpy(c->exten, "s", sizeof(c->exten)-1);
    • c->priority = 1;
    • context, exten, priority 가 같은 것이 없으면, context 는 default, exten 은 s, priority 는 1 로 설정
  • }
  • for(;;) {
    • pos = 0; digit = 0;
    • while(ast_exists_extension(c, c->context, c->exten, c->priority, c->callerid)) {
      • if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->callerid))) {
        • dialplan(extensions.conf) 의 application 이 수행된다.
        • goto out;
      • }
      • if (c->stream) {
        • digit = ast_waitstream(c, AST_DIGIT_ANY);
        • ast_stopstream(c);
        • if (digit < 0)
          • goto out;
        • else if (digit) {
          • ast_stopstream(c);
          • extenpos++ = digit;
          • break;
        • }
      • }
      • firstpass = 0;
      • c->priority++;
    • }
    • while(!ast_exists_extension(c, c->context, exten, 1) && (
      strlen(exten) < ast_pbx_longest_extension(c->context))) {
      • digit = ast_waitfordigit(c, waittime * 1000);
      • if (!digit)
        • break;
      • if (digit < 0)
        • goto out;
      • extenpos++ = digit;
      • waittime = c->pbx->dtimeout;
    • }
    • if (ast_exists_extension(c, c->context, exten, 1)) {
      • strncpy(c->exten, exten, sizeof(c->exten));
      • c->priority = 1;
    • } else {
      • if (strlen(exten)) {
        • An invalid extension
        • if (ast_exists_extension(c, c->context, "i", 1)) {
        • } else {
          • Invalid extension, but no rule 'i' in context
          • goto out;
        • }
      • } else {
        • A simple timeout
        • if (ast_exists_extension(c, c->context, "t", 1)) {
          • strncpy(c->exten, "t", sizeof(c->exten))
          • c->priority = 1;
        • } else {
          • Timeout, but no rule 't' in context
          • goto out;
        • }
      • }
    • }
  • }
  • out:
  • pbx_destroy(c->pbx);
  • c->pbx = NULL;
  • ast_hangup(c);
  • pthread_exit(NULL)



sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2009-07-05 15:41:23
Processing time 0.0338 sec