Asterisk 1.8 sip 協議棧分析

引用自:http://blog.csdn.net/z1623866465/archive/2011/01/02/6113057.aspx
css

看了一下 asterisk 1.8 ,chan_sip 更新了許多內容,下面結合asterisk 1.4 asterisk 1.6 分析一下sip協議棧。數據庫

此筆記爲本人學習記錄,有些地方描述其餘人可能看不懂,望見諒。服務器

分析路線網絡

sipsock_read->parse_request->find_call->handle_inconming->handle_request_方法名。。。。session

協議棧初始化:load_module() 函數加載SIP配置信息,解析sip.conf掛載到全局變量中。併發

     首先初始化user,peer,register全局鏈表(1.6 版本中已經改成hash存儲 估計性能提升很多),這三個鏈表分別存儲用戶,peer,register三個實體。app

   接下來建立 調度器,IO管理器,這裏IO即監聽socket fd句柄上的IO事件,chan_sip用poll異步IO實現此功能,io_context 結構封裝了此功能。dom

 建立IO調度器後註冊各類app, load_module()最後調用restart_monitor()函數建立一個線程(do_monitor())監聽sip 端口(5060)上的事件, 在do_monitor()函數中首先將sip socket 句柄添加到以前建立的IO管理器中,同時指定此sip socket句柄上的IO事件對應的回調函數(sipsock_read),即當監聽的sip socket 句柄上有事件發生時調用 sipsocket_read函數,此函數是全部sip包的入口,負責接收監聽端口(5060)上的數據包。do_monitor函數接下來,遍歷全局sip channle 鏈表iflock ,遍歷的同時 考慮 channle 的重載 (cli reload), ,掛斷那些不符合規則的sip channel,好比 rtp 超時,onhold rtp 超時、釋放sip channle 資源(調用sip_destroy()函數), 鎖的釋放,rtp,vrtp,udps 等 結構的釋放。異步

如今回到 sipsock_read函數:socket

sipsock_read 函數調用 經典的udp socket 函數 recvfrom(1.8版本中已將這些經典函數封裝) 讀取網絡數據包,保存到buffer中,

 此函數中聲明sip_request 結構,此結構以下:

struct sip_request {

ptrdiff_t rlPart1;     
 ptrdiff_t rlPart2;     
 int len;               
 int headers;           
 int method;            
 int lines;             
 unsigned int sdp_start;
 unsigned int sdp_count;
 char debug;            
 char has_to_tag;       
 char ignore;           
 ptrdiff_t header[SIP_MAX_HEADERS];
 ptrdiff_t line[SIP_MAX_LINES];    
 struct ast_str *data; 
 struct ast_str *content;
 
 struct sip_socket socket;         
 AST_LIST_ENTRY(sip_request) next;
};

對於每個udp數據都做爲爲是一次請求,此結構封裝了sip 協議的消息頭,方法,body,及此請求的socket 句柄,保存這次請求數據的data字段,此處只是將sipsock_read讀到的數據包保存到sip_request 結構的 data字段,數據包的處理留給 parse_request()函數處理。

sipsock_read函數接下來調用handle_request_do(&req, &addr); 函數處理這次請求,參數爲sip_request 結構及此此數據包的來源。

handle_request_do(&req, &addr) 函數內部首先 對 pedanticsipchecking 處理,此處解釋一下 pedanticsipchecking

pedanticsipchecking 爲sip.conf文件的一個配置選項,表示是否開啓將多個請求頭放在一個請求頭裏的狀況,咱們知道sip消息由若干個消息頭(from, to,contact 等)組成,每一個請求頭部用\r\n分隔,可是有些狀況下全部sip輕輕頭放在一個輕輕頭部,而沒有用\r\n分隔,因此當pedanticsipchecking 設置爲yes時handle_request_do 會調用lws2sws函數處理此種狀況,若是不顯示設置pedanticsipchecking  爲no,貌似1.8中默認爲yes了,若是將sip debug打開,

if (req->debug) {
  ast_verbose("\n<--- SIP read from %s:%s --->\n%s\n<------------->\n",
   get_transport(req->socket.type), ast_sockaddr_stringify(addr), req->data->str);
 }

此處應該能夠看到數據包的內容,在個人平臺上

註冊消息 在handle_request_do 內部打印內容以下:

<--- SIP read from UDP:10.10.10.84:51126 --->
REGISTER sip:10.10.10.182 SIP/2.0
Via: SIP/2.0/UDP 10.10.10.84:51126;branch=z9hG4bK-d87543-b525b54eb12a8030-1--d87543-;rport
Max-Forwards: 70
Contact: <sip:15011599734@10.10.10.84:51126;rinstance=aa35be9f21f51771>
To: "15011599734"<sip:15011599734@10.10.10.182>
From: "15011599734"<sip:15011599734@10.10.10.182>;tag=dc3d5677
Call-ID: OWIyMWViOThiZjQ3YmEwNTFhNzg3YzA1M2Q3ZTRhMDM.
CSeq: 1 REGISTER
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: X-Lite release 1011s stamp 41150
Content-Length: 0

handle_request_do 函數接下來調用parse_request()函數正式解析數據包爲rfc3261規定的sip格式,同時根據rfc3261定義作sip 包的額外檢查,parse_request() 即解析進來的數據包也解析系統發出的數據包。

parse_request() 函數內部按CRLF 解析數據包(res->data),rfc3261規定SIP消息頭與消息體(通常爲sdp,presens或im時 爲xml數據)之間要用一個空行來分隔,對於一個PUBLISH請求具體形式 以下:

<--- SIP read from UDP:10.10.10.84:51126 --->
PUBLISH sip:15011599734@10.10.10.182 SIP/2.0
Via: SIP/2.0/UDP 10.10.10.84:51126;branch=z9hG4bK-d87543-3d691e75b7321d47-1--d87543-;rport
Max-Forwards: 70
Contact: <sip:15011599734@10.10.10.84:51126>
To: "15011599734"<sip:15011599734@10.10.10.182>
From: "15011599734"<sip:15011599734@10.10.10.182>;tag=20557655
Call-ID: YWQ1OTg4ZDg2MGFiMGUwMDk5YjI1NTYzMzQwNTc4Y2I.
CSeq: 1 PUBLISH
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
Content-Type: application/pidf+xml
User-Agent: X-Lite release 1011s stamp 41150
Event: presence
Content-Length: 463

<?xml version='1.0' encoding='UTF-8'?><presence xmlns='urn:ietf:params:xml:ns:pidf' xmlns:dm='urn:ietf:params:xml:ns:pidf:data-model' xmlns:rpid='urn:ietf:params:xml:ns:pidf:rpid' xmlns:c='urn:ietf:params:xml:ns:pidf:cipid' entity='sip:15011599734@10.10.10.182'><tuple id='t733b0f62'><status><basic>open</basic></status></tuple><dm:person id='p0725b86e'><rpid:activities><rpid:on-the-phone/></rpid:activities><dm:note>On the Phone</dm:note></dm:person></presence>
<------------->

加黑部分爲消息頭,下面爲消息體,此處爲軟電話的presence 攜帶狀態消息,<status><basic>open</basic></status> ,表示當前在線。能夠看到消息頭與消息體有一行空格分隔。parse_request()函數接下來會計算消息頭數量以及消息體行數,而後保存到sip_request結構的 req->headers屬性及req->req->lines屬性上,最後parse_request()調用determine_firstline_parts()從sip 消息的第一個頭域解析出  方法名、協議版本、及請求地址賦值給req->rlPart1及req->rlPart2屬性,同時還會對sip消息的態碼驗證是否爲三位,協議版本是否爲SIP/2.0,不符合協議要求則返回錯誤。

如今返回到 handle_request_do()函數,調用parse_request返回後,調用find_sip_method 解析sip_request的rlpart1屬性找出方法名字,解析包結束後從debug消息能夠輸出消息頭及消息體的行數

if (req->debug)
  ast_verbose("--- (%d headers %d lines)%s ---\n", req->headers, req->lines, (req->headers + req->lines == 0) ? " Nat keepalive" : "");

--- (14 headers 1 lines) ---

正常的sip消息頭至少兩行,因此這裏一樣作了驗證,接下來調用find_call (),此函數的做用爲查找此請求的 CALL-ID,若是沒找到則建立之,咱們知道sip協議裏有兩個重要概念 會話及事務,會話是一路通話的惟一標識,會話用CALL-ID及from,to tag標示,事務是會話過程當中某一請求及與此請求對應的效應的標識,能夠認爲會話包含事務,由於一路通話可能包含不少請求。

下面咱們來看find_call(),對於第一次進入系統則call-ID不存在,因此find_call會建立此會話,若是請求的CALL-ID在系統中存在,則將此請求與存在的會話對接。

首先 find_call 今後次請求的sip包中解析出call-ID, From, to,Cseq 頭域,實際上rfc3261要求每一個sip包至少還要包含Max-forwoards及VIa 頭域,但 chan_sip彷佛沒對上面兩項驗證,這裏若是驗證不經過則發 400 bad request 響應。

 原則上講 CALL-ID 惟一標識一路通話,可是在一些代理服務器fork 時候 因爲CAll-ID相同,因此要用to,from tag 惟一標識,若是有這種狀況咱們應該在sip。conf文件中打開pedanticsipchecking 選項 爲 yes時 find_call只是根據 CALL-ID查找是否存在,爲no時根據call-id,to,from tog 查找 會話是否存在,協議要求全部sip消息必定有from tag, 同時,ACK, BYE消息必定要有to tag, 不然 sip 消息會被丟棄。對於from。to, tag 的解釋 有一點很重要,即在響應消息中,響應者不會將 from 及to 調換,由於to,from頭域用來提示請求的方向而不是消息的流向,好比 bob 發請求給tom,那麼全部響應中的from爲bob,to域爲tom.

find_call 找到會話後會直接返回此會話的channle, 不然建立sip_pvt 結構並返回。1.4 版本中查找會話方式是用過遍歷會話鏈表,而1.6及1.8中已經替換爲 hash方式,性能應該提升不少。

若是沒找到會話則建立 channle, 建立時會 考慮 哪些方法支持 建立channel,這裏 ACK,PRACK,BYE,INFO,CANCEL,update方法是不可建立channle的,咱們知道這些方法都是在會話已經存在時可用。若是方法不能建立會話則會響應協議自己不支持方法,返回 501 Mothed not implement, 500 server internal error.

這裏建立sip channel 結構 sip_pvt,此結構對應一路會話的全部信息,包括 註冊,協商,呼叫。

經過 sip_alloc()建立sip_pvt結構,並將其 添加進全局會話鏈表 dialloglist。

下面 來分析 sip_alloc : 此函數的參數爲 call-id, 前面初始化的sip_request結構,以及方法,

首先調用ao2_t_alloc 申請 對象sip_pvt內存,同時指定 垃圾回收回調函數(1.6,1.8 新增),

 這裏1.8增長了 CCSS Call complete supplementary services ,因此分配 ccss結構。

若是此回話爲請求會話,則存儲 請求的參數到sip_pvt屬性中。包括Cseq,及branch,這裏sip協議要求全部branch要以.z9hG4bK 開始,

接下來給sip_pvt 屬性賦值,包括 transport type, 此會話的默認 編碼(全局配置),max_forwords 最大跳數(全局默認),

sip  事務 定時器(全局 timer_t1),sip 消息轉發定時器(全局配置 timer_b), 此消息客戶端 ip地址(),若是輕輕地客戶端地址爲 空則拷貝默認地址(全局配置),不然將客戶端地址賦值給p->ourip, 若是sip 方法支持 rtp則設置 比特率(通常碼率越高質量越高,錄音文件越大),這裏sip_alloc有個參數 useglobal_nat,傳進來時爲1,涉及到nat穿透問題,表示 強迫 rport,即便請求中沒有 rport (via 頭域),接下來調用do_setnat(p)rtp, vrtp,udpl 的nat 模式是否開啓,設置 關於nat 穿透及rport解釋 參考另外一篇文章 SIP經過NAT的實例解析,接下來設置 FROm 頭域中的 域名(全局默認),而後調用built_via 構造via頭域p->via,

接下來設置默認等待音樂,是否支持轉移(allowtransfer),編碼轉換(capability),context,,默認的parklot, 最後 sip_alloc 函數調用工具函數ao2_t_link 把建立的會話插入 全局會話容器(1.4爲鏈表)dialogs。同時把此請求放到請求隊列中。

 find_call 結束,返回handle_request_do, 在find_call中將請求放入請求隊列,返回後此處調用process_request_queue處理此會上上的全部請求,下面來看process_request_queue內部作了什麼,此函數不斷遍歷sip_pvt 屬性 request_queue鏈表,從鏈表頭取出一個請求,而後調用 Handle_incoming 處理 請求,開開sip debug,

ast_debug(4, "**** Received %s (%d) - Command in SIP %s\n", sip_methods[p->method].text, sip_methods[p->method].id, cmd);

對於 註冊請求輸出以下:

Dec 16 14:06:58] DEBUG[17316]: chan_sip.c:23539 handle_incoming: **** Received REGISTER (2) - Command in SIP REGISTER

Handle_incoming 是全部sip 請求的畢經入口,下面看看內部作了什麼,首先檢查sequence 是否正確,從請求頭中找出User-Agent 保存客戶端,接下來 分析 這次請求的 方法, 根據不一樣的方法調用Handle_request_方法名 具體處理請求, 包含以下一些 方法的處理函數。

 
 switch (p->method) {
 case SIP_OPTIONS:
  res = handle_request_options(p, req, addr, e);
  break;
 case SIP_INVITE:
  res = handle_request_invite(p, req, debug, seqno, addr, recount, e, nounlock);
  break;
 case SIP_REFER:
  res = handle_request_refer(p, req, debug, seqno, nounlock);
  break;
 case SIP_CANCEL:
  res = handle_request_cancel(p, req);
  break;
 case SIP_BYE:
  res = handle_request_bye(p, req);
  break;
 case SIP_MESSAGE:
  res = handle_request_message(p, req);
  break;
 case SIP_PUBLISH:
  res = handle_request_publish(p, req, addr, seqno, e);
  break;
 case SIP_SUBSCRIBE:
  res = handle_request_subscribe(p, req, addr, seqno, e);
  break;
 case SIP_REGISTER:
  res = handle_request_register(p, req, addr, e);
  break;
 case SIP_INFO:
  if (req->debug)
   ast_verbose("Receiving INFO!\n");
  if (!req->ignore)
   handle_request_info(p, req);
  else 
   transmit_response(p, "200 OK", req);
  break;
 case SIP_NOTIFY:
  res = handle_request_notify(p, req, addr, seqno, e);
  break;
 case SIP_UPDATE:
  res = handle_request_update(p, req);
  break;
 case SIP_ACK:
 
  if (seqno == p->pendinginvite) {
   p->invitestate = INV_TERMINATED;
   p->pendinginvite = 0;
   acked = __sip_ack(p, seqno, 1 , 0);
   if (find_sdp(req)) {
    if (process_sdp(p, req, SDP_T38_NONE))
     return -1;
   }
   check_pendings(p);
  } else if (p->glareinvite == seqno) {
  
   p->glareinvite = 0;
   acked = __sip_ack(p, seqno, 1, 0);
  }
  if (!acked) {
  
   p->method = oldmethod;
  }
  if (!p->lastinvite && ast_strlen_zero(p->randdata)) {
   pvt_set_needdestroy(p, "unmatched ACK");
  }
  break;
 default:
  transmit_response_with_allow(p, "501 Method Not Implemented", req, 0);
  ast_log(LOG_NOTICE, "Unknown SIP command '%s' from '%s'\n",
   cmd, ast_sockaddr_stringify(&p->sa));
 
  if (!p->initreq.headers) {
   pvt_set_needdestroy(p, "unimplemented method");
  }
  break;
 }

 下面分析  當請求方法爲 register時handle_request_register作了什麼。。。

路線:

handle_request_register-->register_verify->find_peer()->check_auth()->parse_register_contact()->transmit_response_with_date().

 首先,調用copy_request 複製此請求到p->inireq中,此屬性代表這次請求爲一個已經存在的對話的請求,而不是新建立的會話的請求,

 主要用來作 將來的響應消息 調用(p->inireq),接下來handle_request_register 調用 register_verify

 
static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr,
           struct sip_request *req, const char *uri)

 調用parse_uri從請求頭中取出 To 頭域,解析 To頭域的請求 URI,分解出域名,用戶名,而後設置分機號碼 爲用戶名,

調用build_contact 構造 constact 域,而後根據分機號碼調用 find_peer ,查看是否已存在,find_peer能夠根據ip地址及分機號碼查找peer,此處註冊時根據分機號碼查找,比我我想註冊號碼 1234到 服務器10.10.10.134,則查找1234,須要注意的是此函數可能查數據庫(用 realtime時),固然了,第一次註冊確定找不到,因此爲了簡化咱們不過多討論。

根據 find_peer返回結果 會作不一樣處理:

1:找到peer ,但沒有設置爲 host=dynamic 則返回AUTH_PEER_NOT_DYNAMIC,若是設置host=dynamic ,則接下來會調用

 check_auth作驗證,若是transport 錯誤則返回403 forbidden,  驗證成功則 根據這次註冊請求更新peer(由於找到peer因此已經認證,此時只更新peer),調用parse_register_contact 解析 contact頭域作更新動做 根據不一樣結果 調用transmit_response_with_date 作響應,這裏,當響應是200ok時把contact域的地址換成了received地址,同時,當不指定強制使用rport 端口時會根據contact uri中的域名解析ip地址及端口號,若是在 請求的uri中有端口號則不會作域名解析,同時根據transport 類型指定 端口,當爲uri時端口爲5060,uris時5061,

在不用請求的uri中的端口時 直接將rport端口指定到contact uri的端口上,這一點對於sip nat穿透很重要。

接下來parse_register函數驗證此peer的IP地址是否爲sip.conf裏面配置的禁止註冊ip地址,這點能夠防止註冊攻擊,

接下來把此peer的ip地址放入peers_by_ip全局鏈表中,以便之後經過ip地址查找此peer, 接下來處理此peer的存活時間,若是是realtime peer且開啓 catche 功能(sip.conf配置),則增長此peer的存活時間。

peer->expire = ast_sched_add(sched, (expire + 10) * 1000, expire_register,
    ref_peer(peer, "add registration ref"));

同時指定 回調函數 expire_register,當peer存活時間到期時調用它來銷燬peer,這裏(expire_register)又調用destroy_association 從數據庫或ast_db中刪除 peer註冊信息,首先,destroy_association  調用int realtimeregs = ast_check_realtime("sipregs"); 返回是否存在sipregs表,不存在則指定爲sippeers表,接下來又有一個特殊狀況,即當sip。conf中配置了 ignor_regexpire(即不考慮peer存活時間)時,此函數不會刪除peer註冊信息,同時,若是開啓了 rtupdate(sip.conf)選項,則只是用peer的註冊信息更新數據庫內容。除了以上狀況,則調用 ast_db_del("SIP/Registry", peer->name);
   ast_db_del("SIP/PeerMethods", peer->name); 刪除peer信息。

同時peer信息會從內存中被刪除,包括realtime peer.

下面返回註冊成功信息,

註冊成功則將peer信息放到ast_db中,。parse_register_contact函數接下來調用sip_poke_peer 判斷peer的可達性,保持nat開啓,而後調用register_peer_exten 把peer分機號加入dialplan中。

最後返回到 register_verify函數調用 transmit_response_with_date對這次請求作響應。

2:沒找到peer且sip.conf文件中autocreatpeer選項開啓(若是沒找到peer ,則自動建立peer,默認配置爲 no),

  

根據 分機號碼 (exten)建立peer結構,鏈接到 全局鏈表peers中,

 注意 此處若是是註冊更新則會觸發ami 事件 PeerStatus

manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Registered\r\nAddress: %s\r\n", peer->name, ast_sockaddr_stringify(addr));

3: 沒找到peer,sip.conf中 alwaysauthreject 開啓(請求失敗發 401 未認證響應)

 

 

 

 

從handle_request_invite入口,invite請求此到處理replace請求頭,若是爲replace則認爲是諮詢,此時不會建立新 的通道,而是找到一個通道植入(masqued),大多數狀況下是根據invite建立新的請求,因此此處咱們從這裏開始,不考慮諮詢狀況

首先檢查此請求是否爲重複請求,if (!req->ignore) ,接下來調用check_via檢查via頭域,這個函數涉及到nat穿越問題,此函數解析rport頭域參數,若是via頭域有rport= ,則設置標記此請求包含rport 域標誌,同時檢查maddr= 域是否存在,若是此處rport=存在則設置nat mode 爲 nat,不然爲no nat, 至此check_via結束。

返回 invite函數,這裏 invite 有兩種狀況,一個爲 call-id已經存在,則asterisk認爲此請求是re-invite(!p->owner),不然認爲是一個新的invite,關於re-invite有不少故事,涉及到asterisk是b2bua仍是proxy的問題,下面先討論非 re-invite請求。

從打印信息看到

ast_verbose("Using INVITE request as basis request - %s\n", p->callid);

Using INVITE request as basis request - ZjRiYjZkYzYzZDNjNDRmMjhmMmNlNzdmODE4NTYzZmE.

若是開啓sip history 能夠看到會調用,

append_history(p, "Invite", "New call: %s", p->callid);

接下來調用parse_ok_contact ()函數保存 此invite的contact頭域,以備未來作響應(200 ok,bye, re-invite)

fullcontact 變量保存 所有cantact頭域,用來作bye,re-invite,okcontacturi保存uri of acks, bye, re-invite.

 接下來調用 下面代碼:

if (!p->lastinvite && !req->ignore && !p->owner) {        // 全新invite
 
 
  int cc_recall_core_id = -1;
  set_pvt_allowed_methods(p, req);
  res = check_user_full(p, req, SIP_INVITE, e, XMIT_RELIABLE, addr, &authpeer);
  if (res == AUTH_CHALLENGE_SENT) {
   p->invitestate = INV_COMPLETED; 
   res = 0;
   goto request_invite_cleanup;
  }

對於第一次請求會作驗證,調用check_user_full,下面分析一下此函數。

此函數用 請求的 from 頭域 的usr name 和 peer的 ip/port作匹配,check_user_full 調用get_calleridname 從from頭域分理出

callid_name,最終調用 check_peer_ok ,check_peer_ok內部查找 peer name 是否在 peers 鏈表中存在。這裏先嚐試用

from 頭域中的 user name查找,找不到則用 ip/port查找。

這裏找到後 輸出以下。

if (debug)
  ast_verbose("Found peer '%s' for '%s' from %s\n",
   peer->name, of, ast_sockaddr_stringify(&p->recv));

Found peer '1501159973' for '1501159973' from 10.10.10.84:59584

而後 把peer 相關屬性拷貝到爲此peer建立的channle中,如 acc,language,amaflags。callgroup,fullcontact等,

設置定時器管理 事務。。。

接下來調用 dialog_initialize_rtp函數初始化此peer的rtp信息。

是否peer有RTP,有則設置編碼。設置RTP 引擎,這裏須要說明的是 asterisk1.8開始RTP協議棧改動很大,默認使用Asteirsk提供的rtp協議棧,開發者能夠本身嵌入其餘rtp協議棧。

設置完協議棧後設置此peer 創建rtp會話的默認編碼規則。

Found peer '1501159973' for '1501159973' from 10.10.10.84:59584
[Dec 21 15:30:13] DEBUG[28437]: rtp_engine.c:344 ast_rtp_instance_new: Using engine 'asterisk' for RTP instance '0xc1c0078'
[Dec 21 15:30:13] DEBUG[28437]: res_rtp_asterisk.c:472 ast_rtp_new: Allocated port 18084 for RTP instance '0xc1c0078'
[Dec 21 15:30:13] DEBUG[28437]: rtp_engine.c:353 ast_rtp_instance_new: RTP instance '0xc1c0078' is setup and ready to go
[Dec 21 15:30:13] DEBUG[28437]: res_rtp_asterisk.c:2370 ast_rtp_prop_set: Setup RTCP on RTP instance '0xc1c0078'

此處路線: check_peer_ok->dialog_initialize_rtp->ast_rtp_instance_new->ast_rtp_instance_set_timeout

ast_rtp_instance_set_hold_timeout

ast_rtp_instance_set_prop

ast_rtp_instance_set_qos

do_setnat

上面爲一些列調用過程,初始化此peer的RTP信息,包括 qos,nat mode,rtcp,dtmf幾項任務。check_peer_ok函數作了不少工做哇。。。。

至此 驗證經過,RTP信息也初始化完畢,返回 handl_request_invite函數 ,開始處理 SDP啦。。。。


  if (find_sdp(req)) {

if (process_sdp(p, req, SDP_T38_INITIATE)) {
   
    if (!ast_strlen_zero(get_header(req, "Content-Encoding"))) {
     transmit_response_reliable(p, "415 Unsupported Media type", req);
    } else {
    
     transmit_response_reliable(p, "488 Not acceptable here", req);
    }

。。。。

能夠看到,開始處理SDP...

首先固然是從invite請求中找sdp 了,,調用find_sdp (),


static int find_sdp(struct sip_request *req)

此處固然是找到了。。

調用 process_sdp 處理SDP,


static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action)

解析SDP包頭。。。

[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8197 process_sdp: Processing session-level SDP v=0... UNSUPPORTED.
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8197 process_sdp: Processing session-level SDP o=- 0 2 IN IP4 10.10.10.84... UNSUPPORTED.
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8197 process_sdp: Processing session-level SDP s=CounterPath X-Lite 3.0... UNSUPPORTED.
[Dec 21 15:30:13] DEBUG[28437]: netsock2.c:125 ast_sockaddr_split_hostport: Splitting '10.10.10.84' gives...
[Dec 21 15:30:13] DEBUG[28437]: netsock2.c:155 ast_sockaddr_split_hostport: ...host '10.10.10.84' and port '(null)'.
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8197 process_sdp: Processing session-level SDP c=IN IP4 10.10.10.84... OK.
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8197 process_sdp: Processing session-level SDP t=0 0... UNSUPPORTED.

首先掃描 m=頭域,media stream

這裏包含一些SDP 信息,解釋以下


SDP Data from Example  SDP Parameter
 Parameter Name
 
v=0 
 Version number
 
o=Tesla 2890844526 2890844526 IN IP4 lab.high-voltage.org 
 Origin containing name
 
s=Phone Call 
 Subject
 
c=IN IP4 100.101.102.103 
 Connection
 
t=0 0 
 Time
 
m=audio 49170 RTP/AVP 0 
 Media
 
a=rtpmap:0 PCMU/8000 
 Attributes
 


•Connection IP address (100.101.102.103);

•Media format (audio);

•Port number (49170);

•Media transport protocol (RTP);

•Media encoding (PCM μ Law);

•Sampling rate (8,000 Hz).

每一個SDP頭域包含 m,o,c,等對不一樣SDP頭域的處理,

每種類型調用 process_sdp_類型 函數處理,

如 鏈接地址 處理函數process_sdp_c,這裏最重要的是 process_sdp_a_媒體類型,此函數處理SDP包的屬性,如音頻,則爲

process_sdp_a_audio,關於SDP處理的核心位置都在這個函數中。。。process_sdp根據SDP包屬性(媒體編碼類型)匹配支持的編碼類型。

[Dec 21 15:30:13] DEBUG[28437]: rtp_engine.c:535 ast_rtp_codecs_payloads_set_m_type: Setting payload 98 based on m type on 0xb7b30490
Found RTP audio format 8
[Dec 21 15:30:13] DEBUG[28437]: rtp_engine.c:535 ast_rtp_codecs_payloads_set_m_type: Setting payload 8 based on m type on 0xb7b30490
Found RTP audio format 101
[Dec 21 15:30:13] DEBUG[28437]: rtp_engine.c:535 ast_rtp_codecs_payloads_set_m_type: Setting payload 101 based on m type on 0xb7b30490
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=alt:1 1 : Bwd30+5D C9DdG2tq 10.10.10.84 50946... UNSUPPORTED.
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=fmtp:101 0-15... UNSUPPORTED.
Found audio description format BV32 for ID 107
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:107 BV32/16000... OK.
Found audio description format BV32-FEC for ID 119
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:119 BV32-FEC/16000... OK.
Found audio description format SPEEX for ID 100
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:100 SPEEX/16000... OK.
Found audio description format SPEEX-FEC for ID 106
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:106 SPEEX-FEC/16000... OK.
Found audio description format SPEEX-FEC for ID 105
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:105 SPEEX-FEC/8000... OK.
Found audio description format iLBC for ID 98
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:98 iLBC/8000... OK.
Found audio description format telephone-event for ID 101
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=rtpmap:101 telephone-event/8000... OK.
[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:8384 process_sdp: Processing media-level (audio) SDP a=sendrecv... OK.

至此,關於此peer的 SDP信息及 RTP信息都已初始化完畢。

回到 handle_request_invite()


  ast_debug(1, "Checking SIP call limits for device %s\n", p->username);

開始 檢查 peer的 call-limit

若是此peer達到 上限,則返回 480 Temporarily Unavailable (Call limit)響應。。。因此當調試時返回此響應咱們應該猜想到 此設備已經達到併發上限。。。。

接下來 調用get_destination(),咱們得給此請求 送到哪????

此函數用 invite請求的 to 頭域 做爲請求地址,

根據請求的peer name@ip 查找請求的peerr是否在我這check_sip_domain()。。。找不到則到dialplan中查找。。。。。


  if (!ast_test_flag(&p->flags[1], SIP_PAGE2_HAVEPEERCONTEXT) && !ast_strlen_zero(domain_context)) {
   ast_string_field_set(p, context, domain_context);
  }

這裏是當 咱們在sip config 裏設置潤許guest invite時設置 context爲默認。。

當在dialplan中找到對應分機時咱們得給此SIP 請求 建立 channle啦。。

調用 sip_new()...這裏,sip_new函數是真正建立sip通道的地方,此函數 在呼入請求,及外呼請求時調用,分別爲函數 sip_request_call及handle_request_invite函數。。。

sip_new函用 sip_pvt結構建立sip structuer, 設置此通道的編碼類型,dtfm, caller id等信息。。。

調用ast_channel_alloc 宏(channel.c)建立sip channle,建立細節在__ast_channel_alloc_ap 函數中,channle的分配用

ao2_alloc函數,同時指定了析構函數釋放通道,申請channel內存後設置 管道句柄初始狀態,建立此channle調度器上下文,

1.8新增了 caller party information 統計信息,因此此處先初始化這些結構,接下來申請channle無名管道,

if (pipe(tmp->alertpipe)) {
   ast_log(LOG_WARNING, "Channel allocation failed: Can't create alert pipe! Try increasing max file descriptors with ulimit -n\n");
   return ast_channel_unref(tmp);
  } else {
   flags = fcntl(tmp->alertpipe[0], F_GETFL);
   if (fcntl(tmp->alertpipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
    ast_log(LOG_WARNING, "Channel allocation failed: Unable to set alertpipe nonblocking! (%d: %s)\n", errno, strerror(errno));
    return ast_channel_unref(tmp);
   }
   flags = fcntl(tmp->alertpipe[1], F_GETFL);
   if (fcntl(tmp->alertpipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
    ast_log(LOG_WARNING, "Channel allocation failed: Unable to set alertpipe nonblocking! (%d: %s)\n", errno, strerror(errno));
    return ast_channel_unref(tmp);
   }
  }

把建立的管道加入fd列表監聽。。。。這裏channle fd數量是有限制的,默認一個channle最大10個。


 ast_channel_set_fd(tmp, AST_ALERT_FD, tmp->alertpipe[0]);
 
 ast_channel_set_fd(tmp, AST_TIMING_FD, tmp->timingfd);

接下來初始化uniqueid,linkeid,這裏uniqueid 最大150個字符,包括系統名字(最大127)+unix時間戳+遞增序列。。。

初始化channle的amaflags,accountcode,context以便於計費使用(cdr)。

接下來 開始 分配此 channle cdr 結構並初始化。。

tmp->cdr = ast_cdr_alloc();// 分配
 ast_cdr_init(tmp->cdr, tmp);//初始化
 ast_cdr_start(tmp->cdr); //設置起始 時間,,,cdr->start..

 ast_cel_report_event(tmp, AST_CEL_CHANNEL_START, NULL, NULL, NULL);// cdel引擎啓動。。發channle start事件。。

1.8多了個CEL ,也是在這裏 初始化。。把channle放到 channles 容內部器。。

channle創建完畢,,,發送ami事件 Newchannel。。完畢後返回 建立channle 到sip_new

sip爲一種通道類型,實際建立爲channle.c中的ast_channle 結構,此結構爲衆多通道的接口層,。。

這裏 設置  SIPURI,SIPDOMAIN,parkinglot,accountcode,language,等全局數據,初始化 fd 事件,值得注意的是  asterisk 1.8中 支持了 epoll 異步Io,在一些狀況下對系統的併發應該提升不少。

接下來 SIP_new 對於有rtp的peer,則初始化 jt引擎(rtp 抖動),

而後,此函數調用 Ast_pbx_start進入Asterisk內核。。。。ast_pbx_start 啓動新的線程處理此channle..

返回後,調用build_route(),記錄 record_Route頭域,此處做用是記錄路由路徑做爲將來的請求。

ast_debug(2, "%s: New call is still down.... Trying... \n", c->name);

至此,channle 已經創建完成,發個臨時響應吧。。。transmit_provisional_response(p, "100 Trying", req, 0);

[Dec 21 15:30:13] DEBUG[28437]: chan_sip.c:21609 handle_request_invite: SIP/1501159973-0000000b: New call is still down.... Trying..

<--- Transmitting (no NAT) to 10.10.10.84:59584 --->
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 10.10.10.84:59584;branch=z9hG4bK-d87543-7c6375234a299e1c-1--d87543-;received=10.10.10.84;rport=59584
From: "1501159973"<sip:1501159973@10.10.10.182>;tag=e648a101
To: "6969 (Softphone)"<sip:6969@10.10.10.182>
Call-ID: ZjRiYjZkYzYzZDNjNDRmMjhmMmNlNzdmODE4NTYzZmE.
CSeq: 2 INVITE
Server: Asterisk PBX 1.8.2-rc1
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH
Supported: replaces, timer
Contact: <sip:6969@10.10.10.182:5060>
Content-Length: 0

這裏,咱們能夠看到,via頭域多了一個received及rport有值了,解決nat穿透。。。

同時記住 100 trying沒有 sdp信息。。 調用 順序,sip_xmit<-send_response<--transmit_response<---transmit_provisional_response<--Handle_request_response.

接下來 執行  dialplan.......至此 一次呼入系統的請求 基本完成

相關文章
相關標籤/搜索