• Asterisk 1.8 sip 协议栈分析


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

    看了一下 asterisk 1.8 ,chan_sip 更新了许多内容,下面结合asterisk 1.4 asterisk 1.6 分析一下sip协议栈。

    此笔记为本人学习记录,有些地方描述其他人可能看不懂,望见谅。

    分析路线

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

    协议栈初始化:load_module() 函数加载SIP配置信息,解析sip.conf挂载到全局变量中。

         首先初始化user,peer,register全局链表(1.6 版本中已经改为hash存储 估计性能提高不少),这三个链表分别存储用户,peer,register三个实体。

       接下来创建 调度器,IO管理器,这里IO即监听socket fd句柄上的IO事件,chan_sip用poll异步IO实现此功能,io_context 结构封装了此功能。

     创建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函数:

    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.......至此 一次呼入系统的请求 基本完成

  • 相关阅读:
    欢迎访问我的快站clone-5483e9466f404.kuaizhan.com
    SWFUpload 2.5.0版 官方说明文档 中文翻译版
    http://demo.jb51.net/js/2011/artDialog/_doc/iframeTop.html
    使用jquery-qrcode生成二维码
    主流浏览器CSS3和HTML5兼容性详细清单
    js获取浏览器基本信息:document.body.clientWidth/clientHeight/scrollWidth/scrollTop。
    document.documentElement和document.body的区别
    CSS 的优先级机制
    css三列布局之双飞翼pk圣杯
    响应式web设计(一)
  • 原文地址:https://www.cnblogs.com/xiaOt119/p/2538725.html
Copyright © 2020-2023  润新知