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"
- #define AST_EVENT_LOG AST_LOG_DIR "/" EVENTLOG
- AST_EVENT_LOG: /var/log/asterisk/event_log
- mkdir(AST_LOG_DIR, 0755);
- eventlog = fopen(AST_EVENT_LOG, "a");
- if (eventlog) {
- return 0;
- } else
- return -1;
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)
}
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;
- 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;
- }
- }
- .so file 戚壱 module list 拭 蒸生檎 {
- }
- while((d = readdir(mods))) {
- } 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;
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 人 尻衣
- category
- fgets(buf, sizeof(buf), f);
- fclose(f);
- return tmp;
- prev 亜 NULL 戚檎, 湛腰属 category 誤聖 return
- prev category 亜 糎仙馬檎, 陥製 category 誤聖 return
- category 亜 糎仙馬檎, 湛腰属 variable 税 pointer return
- 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 研 紫遂敗.
- strcasecmp(v->name, "device")
- ast_channel_register(type, tdesc, AST_FORMAT_G723_1, ixj_request)
- restart_monitor();
- 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;
* static int dial_exec(struct ast_channel *chan, void *data)
- app_dial.c
- tmp->chan = ast_request(tech, chan->format, number);
- channel.c
- c = chan->requester(type, format, 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)
- 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 (!i->owner)
- 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;
- FD_ISSET(i->fd, &rfds)
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");
- 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);
- 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;
- 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);
- if (state == AST_STATE_RING)
- return tmp;
ast_pbx_start ¶
* int ast_pbx_start(struct ast_channel *c)
}
out:
pbx_destroy(c->pbx);
c->pbx = NULL;
ast_hangup(c);
pthread_exit(NULL)
- if (pthread_create(&t, &attr, pbx_thread, c))
- return -1
- return 0
- 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++;
- if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->callerid))) {
- }
- 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;
- }
- }
- if (strlen(exten)) {
- }
He is truly wise who gains wisdom from another's mishap.