对于注册功能,asterisk sip协议栈提供两种服务,
1.asterisk作为sip客户端,注册到其他sip服务器。
2.asterisk作为sip注册服务器,保存客户端注册信息。
下面分析从sip协议栈启动到第二个功能的具体实现:
sip协议栈作为可加载模块在系统启动时动态加载,所以此模块可以根据需要不加载,
每个动态加载模块都由load_module入口
这里负责初始化sip协议栈全局参数及数据结构、
创建调度器,负责sip消息的重发等功能 #
if (!(sched = ast_sched_context_create())) {
ast_log(LOG_ERROR, "Unable to create scheduler context\n");
return AST_MODULE_LOAD_FAILURE;
}
解析配置文件sip.conf#
if (reload_config(sip_reloadreason)) {
/* Load the configuration from sip.conf */
return AST_MODULE_LOAD_DECLINE;
}
注册sip协议栈提供的外部接口(dialplan,ami等)函数,
/* Register AstData providers */
ast_data_register_multiple(sip_data_providers, ARRAY_LEN(sip_data_providers));
/* Register all CLI functions for SIP */
ast_cli_register_multiple(cli_sip, ARRAY_LEN(cli_sip));
/* Tell the UDPTL subdriver that we're here */
ast_udptl_proto_register(&sip_udptl);
。。。。。。
。。。。。
最后调用restart_monitor(); 启动sip协议栈,创建线程do_monitor ,监听 (poll)server socket fd,
有事件时则回调sipsock_read 读取socket数据。
asterisk sip协议栈只负责sip消息的解析,转发,消息的重传,是单线程协议栈,本身的设计并没有遵循sip协议栈的分层架构,
没有事务层,没有状态机,他的消息重传,请求以及响应的事务匹配都是通过一些标记来判断,所以极易出问题,这个问题在asterisk 10计划中有所提及并提出添加transaction机制的方案,由于架构所限,asterisk sip协议栈的性能并不是很高,但是作为b2bua这种即提供信令服务又走媒体流的服务器,sip协议栈的性能并不会影响系统整体并发,而不像sip proxy单纯走信令,此时对sip协议栈的性能有很高要求。
下面分析注册请求处理流程:
客户端发起注册请求,sip包如下:
REGISTER sip:192.168.1.173 SIP/2.0
Via: SIP/2.0/UDP 192.168.11.51:51917;branch=z9hG4bK1498232830;rport
From: <sip:1008@192.168.1.173>;tag=78421215
To: <sip:1008@192.168.1.173>
Contact: <sip:1008@192.168.11.51:51917;transport=udp>;expires=1700;+g.oma.sip-im;language="en,fr";+g.3gpp.smsip;+g.oma.sip-im.large-message;audio;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-application.ims.iari.gsma-vs";+g.3gpp.cs-voice
Call-ID: f9c92870-2103-3504-2526-4623ca8a2357
CSeq: 2028434929 REGISTER
Content-Length: 0
Max-Forwards: 70
Authorization: Digest username="1008",realm="192.168.1.173",nonce="",uri="sip:192.168.1.173",response=""
Allow: INVITE, ACK, CANCEL, BYE, MESSAGE, OPTIONS, NOTIFY, PRACK, UPDATE, REFER, INFO
Privacy: none
P-Access-Network-Info: ADSL;utran-cell-id-3gpp=00000000
User-Agent: VideoPhone 1.0 , Copyright 2011 cyclecentury Inc.
P-Preferred-Identity: <sip:1008@192.168.1.173>
Supported: path
sipsock_read被调用,接收数据报#
res = ast_recvfrom(fd, readbuf, sizeof(readbuf) - 1, 0, &addr);
构造request 结构,调用handle_request_do
此函数处理所有incoming sip 消息,包括sip请求及响应。
解析sip包到sip_request结构,处理基本语法错误
if (parse_request(req) == -1) { /* Bad packet, can't parse */
ast_str_reset(req->data); /* nulling this out is NOT a good idea here. */
return 1;
}
handle_incoming 继续处理此sip消息#
if (handle_incoming(p, req, addr, &recount, &nounlock) == -1) {
/* Request failed */
ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>");
}
handle_incoming根据是sip请求还是响应作不同处理:
/* Find out SIP method for incoming request */
if (req->method == SIP_RESPONSE) {
/* Response to our request */
/* ignore means "don't do anything with it" but still have to
* respond appropriately.
* But in this case this is a response already, so we really
* have nothing to do with this message, and even setting the
* ignore flag is pointless.
*/
。。。。。
。。。。。
/* New SIP request coming in
(could be new request in existing SIP dialog as well...)
*/
p->method = req->method;
/* Find out which SIP method they are using */
ast_debug(4, "**** Received %s (%d) - Command in SIP %s\n", sip_methods[p->method].text, sip_methods[p->method].id, cmd);
对于注册,走请求流程:
case SIP_SUBSCRIBE:
res = handle_request_subscribe(p, req, addr, seqno, e);
break;
case SIP_REGISTER:
res = handle_request_register(p, req, addr, e);
sip_report_security_event(p, req, res);
break;
case SIP_INFO:
if (req->debug)
ast_verbose("Receiving INFO!\n");
if (!req->ignore)
handle_request_info(p, req);
else /* if ignoring, transmit response */
transmit_response(p, "200 OK", req);
break;
handle_request_register(p, req, addr, e); 处理注册请求
register_verify 验证注册请求
通过解析register请求的to头域解析出注册的用户名及注册域名,
To: <sip:1008@192.168.1.173>
1008作为注册用户名。服务器会根据此用户名查找内存及数据库,验证此用户是否存在。
peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0);
sip_find_peer首先在内存里找用户,peers 容器在内存中保存所有注册用户,找不到会查询数据库。
对于在sip.conf 中添加的用户在系统启动时会自动加载到peers容器,所以不会查找数据库,
如果sip.conf中没有配置用户,但realtime engine存在,则会查找数据库表sipregs,
sipregs 表在extconfig.conf中可以配置。
if (!p && (realtime || devstate_only)) {
p = realtime_peer(peer, addr, devstate_only, which_objects);
if (p) {
switch (which_objects) {
case FINDUSERS:
realtime_peer 查询数据库sipregs.
对于配置在sip.conf中的用户不会查询数据库表sipregs.
当找到用户后,验证用户:
check_auth
对于第一次注册,服务器会返回401,未验证响应:
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.11.51:46245;branch=z9hG4bK299292151;received=192.168.11.51;rport=46245
From: <sip:1008@192.168.1.173>;tag=736151247
To: <sip:1008@192.168.1.173>;tag=as4ecd69d3
Call-ID: c2d7ac1e-3470-4775-4412-efcc3adc261f
CSeq: 1593669719 REGISTER
Server: Asterisk PBX UNKNOWN__and_probably_unsupported
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY
Supported: replaces, timer
WWW-Authenticate: Digest algorithm=MD5, realm="asterisk", nonce="18522543"
Content-Length: 0
服务器会在sip消息头添加 WWW-Authenticate头,采用MD5验证,设置服务器所属realm及一个nonce值。
WWW-Authenticate: Digest algorithm=MD5, realm="asterisk", nonce="183658a0"
客户端收到401 未认证后添加消息头 Authorization: 相应字段。
REGISTER sip:192.168.1.173 SIP/2.0
Via: SIP/2.0/UDP 192.168.11.51:46245;branch=z9hG4bK912864128;rport
From: <sip:1008@192.168.1.173>;tag=736151247
To: <sip:1008@192.168.1.173>
Contact: <sip:1008@192.168.11.51:46245;transport=udp>;expires=1700;+g.oma.sip-im;language="en,fr";+g.3gpp.smsip;+g.oma.sip-im.large-message;audio;+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-application.ims.iari.gsma-vs";+g.3gpp.cs-voice
Call-ID: c2d7ac1e-3470-4775-4412-efcc3adc261f
CSeq: 1593669718 REGISTER
Content-Length: 0
Max-Forwards: 70
Authorization: Digest username="1008",realm="asterisk",nonce="183658a0",uri="sip:192.168.1.173",response="8d96b7a24f38fc762cdea53000220323",algorithm=MD5
Allow: INVITE, ACK, CANCEL, BYE, MESSAGE, OPTIONS, NOTIFY, PRACK, UPDATE, REFER, INFO
Privacy: none
P-Access-Network-Info: ADSL;utran-cell-id-3gpp=00000000
User-Agent: VideoPhone 1.0 , Copyright 2011 cyclecentury Inc.
P-Preferred-Identity: <sip:1008@192.168.1.173>
Supported: path
客户端 需要复制服务器响应的nonce值,realm,并添加username,uri, responce字段。
Authorization: Digest username="1008",realm="asterisk",nonce="183658a0",uri="sip:192.168.1.173",response="8d96b7a24f38fc762cdea53000220323",algorithm=MD5
对于认证这块,b2bua和proxy响应头不同,像asterisk这种b2bua 为401响应,消息头为WWW-Authenticate,
客户端认证头为AuthorizatiAuthorization ,
proxy 认证则由407 响应 ,消息头为Proxy-Authenticate,客户端认证头为roxy-Authorization。
认证成功后服务器会给客户端返回200ok响应,其中contact头域标明服务器端保存的客户端地址,某一时刻如果有呼叫到此客户端服务器则根据此地址想此客户端发送请求。
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.11.51:46245;branch=z9hG4bK912864128;received=192.168.11.51;rport=46245
From: <sip:1008@192.168.1.173>;tag=736151247
To: <sip:1008@192.168.1.173>;tag=as3c639d14
Call-ID: c2d7ac1e-3470-4775-4412-efcc3adc261f
CSeq: 1593669718 REGISTER
Server: Asterisk PBX UNKNOWN__and_probably_unsupported
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY
Supported: replaces, timer
Expires: 1700
Contact: <sip:1008@192.168.11.51:46245;transport=udp>;expires=1700
Date: Tue, 20 Mar 2012 02:16:00 GMT
Content-Length: 0
通常客户端注册成功后服务器在contact头域会添加expires 参数,或者添加Expire头,表明此注册的有效时间,
超过此时间服务器会销毁此注册的有效性,所以客户端在注册成功后要启动周期注册功能,每隔expires指定时间发起注册,这通常通过定时器来实现。