Asterisk 1.8 sip 协议栈分析(1)…
来源:互联网 发布:新剑侠传奇网络异常 编辑:程序博客网 时间:2024/06/07 19:19
此笔记为本人学习记录,有些地方描述其他人可能看不懂,望见谅。
分析路线
sipsock_read->parse_request->find_call->handle_inconming->handle_request_方法名。。。。
协议栈初始化:load_module() 函数加载SIP配置信息,解析sip.conf挂载到全局变量中。
现在回到 sipsock_read函数:
sipsock_read 函数调用 经典的udp socket 函数 recvfrom(1.8版本中已将这些经典函数封装)读取网络数据包,保存到buffer中,
struct sip_request {
ptrdiff_trlPart1;
};
对于每一个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
if (req->debug) {
此处应该可以看到数据包的内容,在我的平台上
注册消息 在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/UDP10.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: OWIyMWViOThiZjQ3YmEwNTFh
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/UDP10.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: YWQ1OTg4ZDg2MGFiMGUwMDk5
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'?><presencexmlns='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'><tupleid='t733b0f62'><status><basic>open</basic></status></tuple><dm:personid='p0725b86e'><rpid:activities><rpid:on-the-phone/></rpid:activities><dm:note>OnthePhone</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 消息的第一个头域解析出
现在返回到 handle_request_do()函数,调用parse_request返回后,调用find_sip_method解析sip_request的rlpart1属性找出方法名字,解析包结束后从debug消息可以输出消息头及消息体的行数
if (req->debug)
--- (14 headers 1 lines) ---
正常的sip消息头至少两行,所以这里同样做了验证,接下来调用find_call (),此函数的作用为查找此请求的CALL-ID,如果没找到则创建之,我们知道sip协议里有两个重要概念会话及事务,会话是一路通话的唯一标识,会话用CALL-ID及from,totag标示,事务是会话过程中某一请求及与此请求对应的效应的标识,可以认为会话包含事务,因为一路通话可能包含很多请求。
下面我们来看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 响应。
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 serverinternal 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 新增),
如果此回话为请求会话,则存储请求的参数到sip_pvt属性中。包括Cseq,及branch,这里sip协议要求所有branch要以.z9hG4bK 开始,
接下来给sip_pvt 属性赋值,包括 transport type, 此会话的默认 编码(全局配置),max_forwords最大跳数(全局默认),
sip
接下来设置默认等待音乐,是否支持转移(allowtransfer),编码转换(capability),context,,默认的parklot, 最后 sip_alloc 函数调用工具函数ao2_t_link 把创建的会话插入全局会话容器(1.4为链表)dialogs。同时把此请求放到请求队列中。
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_方法名 具体处理请求, 包含如下一些 方法的处理函数。
路线:
handle_request_register-->register_verify->find_peer()->check_auth()->parse_register_contact()->transmit_response_with_date().
static enum check_auth_result register_verify(struct sip_pvt *p,struct ast_sockaddr *addr,
调用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 ,则接下来会调用
在不用请求的uri中的端口时 直接将rport端口指定到contact uri的端口上,这一点对于sipnat穿透很重要。
接下来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,
同时指定 回调函数expire_register,当peer存活时间到期时调用它来销毁peer,这里(expire_register)又调用destroy_association 从数据库或ast_db中删除peer注册信息,首先,destroy_association
同时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中,
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 未认证响应)
- Asterisk 1.8 sip 协议栈分析(1)…
- Asterisk 1.8 sip 协议栈分析(2)
- Asterisk 1.8 sip 协议栈分析(1)
- VoIP SIPsip概念,sip协议,sip开…
- USB 协议分析 设置USB地址 和 配置…
- Asterisk 1.8 sip 协议栈分析
- Asterisk 1.8 sip 协议栈分析 2
- Asterisk 1.8 sip 协议栈分析(一)
- Asterisk 1.8 sip 协议栈分析(2)
- ZZULI_SummerPractice(3) HDU 1…
- asterisk chan_sip.c代码分析(转…
- Lesson 1 Finding …
- 集线器 交换机 路由器 网桥 …
- Linux 2.4.x 网络协议栈…
- Linux 2.4.x 网络协议栈…
- 基于ARM9 的UDP 协议栈…
- 实验1 :关于FLASHBACK DATABASE …
- USRP Experiment 1: Data transmis…
- java线程_Jsp_servlet_Jdbc/jdo_xml笔试题
- 2011年10月14日
- 2013届应届生求职全程指南
- 励志格言
- Eclipse 在开发中使用到的快捷键
- Asterisk 1.8 sip 协议栈分析(1)…
- visual studio 2008 快捷键
- Asterisk 1.8 sip 协议栈分析(2)
- android
- 当你扛不住的时候就读一遍(转)
- 正则表达式
- 2011年10月25日
- 学习推荐网址
- voip call term