asterisk chan_sip.c代碼分析

1. 代碼簡介:git

Chan_sip.c是SIP協議(RFC3261)的實現代碼,它沒有實現對S/MIME, TCP and TLS的支持,對應的配置文件是sip.conf,代碼所在的分組是:通道驅動類(channel_drivers)。session

    SIP通道處理各類類型的Sip sessions和dialogs(注意:並非全部的dialogs都是「電話呼叫」),主要包括:數據結構

 * - Incoming calls that will be sent to the PBX coreapp

 * - Outgoing calls, generated by the PBXsocket

 * - SIP subscriptions and notifications of states and voicemail messageside

 * - SIP registrations, both inbound and outbound函數

 * - SIP peer management (peerpoke, OPTIONS)oop

 * - SIP text messagesui

 

    在SIP通道中,一般會有一列活躍的SIP dialogs,CLI下的命令sip show channels能夠顯示出大部分dialogs,除了訂閱類的(它們能夠用命令sip show subscriptions顯示出來)。this

CLI命令sip show channels的示例:

Asterisk*CLI> sip show channels

Peer             User/ANR    Call ID      Seq (Tx/Rx)  Form  Hold     Last Message  

192.168.52.245   101         4acf6c1f558  00102/00000  ulaw  No       Tx: ACK                  

192.168.52.246   102         C5B3D616-26  00101/02537  ulaw  No       Rx: ACK                  

192.168.52.245   (None)      MDE0MzI4NTU  00101/00002  unkn  No       Rx: REGISTER              

3 active SIP channels

 

在進行代碼剖析以前,先看一個重要的數據結構sip_pvt.定義在chan_sip.c中,表示一個sip dialog。

sip_pvt這個結構維護了一個sip session的重要數據信息,關鍵字段以下:

struct sip_pvt* next       指向鏈上的下一個sip_pvt結構。

struct ast_channel* owner  指向了擁有這個結構的通道的指針

struct sip_pkt* packets    維護待重傳的sip packet

int pendinginvite          若是有等待的邀請包,則在這裏記下這個包序號

struct ast_rtp* rtp        指向RTP Session的指針

int rtptimeout             rtp的超時時間

struct sockaddr_in sa      對端地址信息

 

2. 代碼剖析:

首先chan_sip模塊註冊了load_module()函數做爲asterisk在加載本模塊時的入口函數。

17818
 (, , "Session Initiation Protocol (SIP)",
17819       .load = ,
17820       .unload = ,17821       .= ,17822 );

load_module()函數讀取配置文件sip.conf,而且註冊一個通道驅動類型,即sip,具體見sip_tech中的結構內容。

17696 if(reload_config(sip_reloadreason)) /* Load the configuration from sip.conf */
17697       return AST_MODULE_LOAD_DECLINE;17699    /* Make sure we can register our sip channel type *17700    if (ast_channel_register(&sip_tech)) {
17701       ast_log(LOG_ERROR, "Unable to register channel type 'SIP'/n");17702       io_context_destroy(io);17703       sched_context_destroy(sched);17704       return AST_MODULE_LOAD_FAILURE;17705    }

Load_module最後調用restart_monitor()來啓動sip監聽。restart_monitor另外還有兩處會被調用,在sip_request_call()和sip_reload()函數體內。

17735    /* And start the monitor for the first time */17736    restart_monitor();

restart_monitor調用pthread接口啓動一個獨立的監聽線程,線程id記錄在monitor_thread,線程入口函數是do_monitor()。

if ((&monitor_thread, NULL, , NULL) < 0) {
      (&monlock);
      (, "Unable to start monitor thread./n");          return -1;
 }

 

do_monitor()給SIP UDP socket添加事件處理器,sipsock_read負責讀取socket收到的數據。

15233    /* Add an I/O event to our SIP UDP socket */15234    if (sipsock> -1)15235       sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL);

do_monitor()函數而後進入一個for(;;)循環中,這個循環不斷檢測是否須要reload sip模塊,而且遍歷sip session列表檢查是否有須要kill的session。它是怎麼遍歷的呢?原來是chan_sip 維護了一個sip_pvt結構的列表,頭指針保存在全局變量iflist中,經過sip_pvt的next域進行遍歷。每一個sip_pvt結構記錄了一個 session的所有信息。

變量t表示如今的時間,sip->lastrtptx表示上次發送rtp包的時間,若是二者之差大於keep alive間隔,則說明須要發送keep alive包了。

15272             if (sip->lastrtptx &&15273                 ast_rtp_get_rtpkeepalive(sip->rtp) &&15274                 (t > sip->lastrtptx + ast_rtp_get_rtpkeepalive(sip->rtp))) {15275                /* Need to send an empty RTP packet */15276                sip->lastrtptx = time(NULL);15277                ast_rtp_sendcng(sip->rtp, 0);15278             }

變量t表示如今的時間,sip->lastrtprx表示上次收到rtp包的時間,若是二者之差大於rpt的timeout間隔,則說明已經超時了。

這兩個超時參數能夠在sip.conf中配置,分別以下:

rtptimeout=60

;rtpholdtimeout=300

if (sip->lastrtprx && (ast_rtp_get_rtptimeout(sip->rtp) || ast_rtp_get_rtpholdtimeout(sip->rtp)) && (t > sip->lastrtprx + ast_rtp_get_rtptimeout(sip->rtp))) {15282                /* Might be a timeout now -- see if we're on hold */

 

此時再檢測holdtimeout,並對channel上鎖,ast_channel_trylock(sip->owner)。若是不是bridged channel,則調用soft hangup。

/* Issue a softhangup */ast_softhangup_nolock(sip->owner, AST_SOFTHANGUP_DEV);

 

如今回過頭來把焦點轉移到sipsock_read()函數。全部到來的sip包都在這裏開始處理,在處理sip包期 間,sipsock_read須要對sip的擁有者channel上鎖,sipsock_read成功則返回0,失敗則返回1。它解析sip包而且找到所 在的dialog,或者建立新的dialog。而且把解析好的包交給handle_request()處理。

    sipsock_read第一步接收socket數據,存到結構sip_request的data域中。

res = recvfrom(sipsock, req.data, sizeof(req.data) - 1, 0, (struct sockaddr *)&sin, &len);

解析SIP包,獲取sip request method,如INVITE, BYE等。

15086    parse_request(&req);
15087    req.method = find_sip_method(req.rlPart1);

隨後找到對應的sip_pvt結構,或者建立新的sip_pvt結構,結構指針返回到變量p中。

15099       /* Find the active SIP dialog or create a new one */15100       p = find_call(&req, &sin, req.method); /* returns p locked */

在進一步操做以前,須要對p->owner上鎖,這個操做會最多嘗試100次直至成功。

15107       /* Go ahead and lock the owner if it has one -- we may need it */15108       /* becaues this is deadlock-prone, we need to try and unlock if failed */15109       if (!p->owner || !ast_channel_trylock(p->owner))15110          break;   /* locking succeeded */

若是上鎖操做失敗,將會返回503 sip消息。

15127       if (req.method != SIP_ACK)15128          transmit_response(p, "503 Server error", &req); /* We must respond according to RFC 3261 sec 12.2 */15129       /* XXX We could add retry-after to make sure they come back */15130       append_history(p, "LockFail", "Owner lock failed, transaction failed.");15131       return 1;

更深一步的解析處理操做交給handle_request()函數處理,完了以後就是釋放channel的鎖。

15134    if (handle_request(p, &req, &sin, &recount, &nounlock) == -1) {15135       /* Request failed */15136       if (option_debug)15137          ast_log(LOG_DEBUG, "SIP message could not be handled, bad request: %-70.70s/n", p->callid[0] ? p->callid : "<no callid>");15138    }15139       15140    if (p->owner && !nounlock)15141       ast_channel_unlock(p->owner);

函數handle_request()視數據包的類型而處理,若是是對外出包的迴應,則交給 handle_response()處理,若是是一個請求包,則視請求類型(INVITE, OPTIONS, REFER, BYE, CANCEL etc)交給不一樣的函數處理。若是是一個INVITE包,則交給handle_request_invite()處理,在那裏將會建立一個新的 channel,這個通道隨後會執行一個單獨的通道線程。這就是一個來電呼叫。若是這個呼叫被應答,則一個橋接通道或者PBX自己會回調 sip_answer()函數。而真正的媒體數據,音頻或者視頻,則會在RTP子系統中處理,具體見rtp.c。

在註冊SIP通道驅動時,咱們註冊了一系列通道驅動的回調函數,這些有什麼用呢?好比當須要發出一個outbound call時,則會調用sip_request_call()。而當須要hangup時,則調用sip_hangup()。

01541 /*! /brief Definition of this channel for PBX channel registration */01542
 static const struct ast_channel_techsip_tech= {01543    .type = "SIP",01544    .description = "Session Initiation Protocol (SIP)",01545    .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1),01546    .properties = AST_CHAN_TP_WANTSJITTER| AST_CHAN_TP_CREATESJITTER,01547    .requester = sip_request_call,01548    .devicestate = sip_devicestate,01549    .call = sip_call,01550    .hangup = sip_hangup,01551    .answer = sip_answer,01552    .read = sip_read,01553    .write = sip_write,01554    .write_video = sip_write,01555    .indicate = sip_indicate,01556    .transfer = sip_transfer,01557    .fixup = sip_fixup,01558    .send_digit_begin = sip_senddigit_begin,01559    .send_digit_end = sip_senddigit_end,01560    .bridge = ast_rtp_bridge,01561    .send_text = sip_sendtext,01562    .func_channel_read = acf_channel_read,01563 };

 

如今開始分析handle_request_invite()函數。檢查invite包中的headers中是否有require,最好是沒有,若是有的話也必須是Replaces,其它的不支持一概不予處理。

13394    /* Find out what they require */13395    required = get_header(req, "Require");13396    if (!ast_strlen_zero(required)) {13397       required_profile = parse_sip_options(NULL, required);13398       if (required_profile && required_profile != SIP_OPT_REPLACES) {13399          /* At this point we only support REPLACES */13400          transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, required);13401          ast_log(LOG_WARNING,"Received SIP INVITE with unsupported required extension: %s/n", required);13402          p->invitestate = INV_COMPLETED;13403          if (!p->lastinvite)13404             sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);13405          return -1;13406       }13407    }

開始驗證sip user的合法性,check_user()調用check_user_full()函數,該函數從heades中的from中取出用戶名並在sip user list 和 sip peer list中匹配,若是沒找着,再查看是否容許guest,若是不容許,則認證通不過。

13584       /* This is a new invite */13585       /* Handle authentication if this is our first invite */13586       res = check_user(p, req, SIP_INVITE, e, XMIT_RELIABLE, sin);

檢查sip包中是否有SDP信息,如: application/sdp 。SDP(Session Description Protocol)是指會話描述協議,SIP包中使用它來描述語音流協議的細節,好比某端所支持的介質編碼(這些編碼使用RTP進行傳輸)。

13558          /* Handle SDP here if we already have an owner */13559          if (find_sdp(req)) {13560             if (process_sdp(p, req)) {13561                transmit_response(p, "488 Not acceptable here", req);13562                if (!p->lastinvite)13563                   sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);13564                return -1;13565             }

檢查該用戶的並行撥打電話數有沒有達到上限。

13633       /* Check number of concurrent calls -vs- incoming limit HERE */13634       if (option_debug)13635          ast_log(LOG_DEBUG, "Checking SIP call limits for device %s/n", p->username);13636       if ((res = update_call_counter(p, INC_CALL_LIMIT))) {13637          if (res < 0) {13638             ast_log(LOG_NOTICE, "Failed to place call for user %s, too many calls/n", p->username);13639             transmit_response_reliable(p, "480 Temporarily Unavailable (Call limit) ", req);13640             sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);13641             p->invitestate = INV_COMPLETED;  13642          }13643          return 0;13644       }

查找對應的extension,若是沒有對應的extension,則從extension s開始執行(extension s是默認的extension,s表示start)。

13645       gotdest = get_destination(p, NULL); /* Get destination right away */

調用sip_new()建立channel,這時候是incoming call。當調用dial application發起outbound call時asterisk pbx根據註冊的回調函數sip_request_call()一樣進入到sip_new中建立channel。

13672          /* First invitation - create the channel */13673          c = sip_new(p, AST_STATE_DOWN, S_OR(p->username, NULL));

調用ast_pbx_start(),該函數啓動一個獨立線程負責這個channel,線程函數是pbx_thread(),pbx_thread()調用__ast_pbx_run()函數。

13717             res = ast_pbx_start(c);

__ast_pbx_run()函數allocate 一個pbx結構和cdr結構,並把它們的指針保存到ast_channel結構的pbx域和cdr域。隨後進入for循環逐個執行application。具體見./main/pbx.c。

02385       /* loop on priorities in this context/exten */02386       while (ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) {02387          found = 1;02388          if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->cid.cid_num))) {02389             /* Something bad happened, or a hangup has been requested. */

下面再來分析下handle_request_bye()函數,這個函數比較簡單,它在收到BYE包時被觸發,首先記錄 下rtp, vrtp的qos到channel內置變量,調用stop_media_flows(p)結束rtp流,調用 ast_queue_hangup(p->owner)進行掛斷操做,調用transmit_response(p, "200 OK", req)返回200 OK消息。其中ast_queue_hangup()調用ast_queue_frame()在ast_channel機構的ast_frame隊列裏插 入一個HANGUP的幀

相關文章
相關標籤/搜索