引用自: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.......至此 一次呼入系統的請求 基本完成