freeswitch注册过程分析

操作系统:debian8.5_x64
freeswitch 版本 : 1.6.8

本文仅描述sip注册的简单场景,即话机直接向处于同一个局域网的fs进行注册。

SIP协议的消息结构

消息框架

SIP协议是基于文本的协议,SIP协议的消息都遵从一个统一的消息结构: 起始行(Start-Line)、一个或多个头域(Message-Header)、表明域结束的空行(CRLF),以及 可选的消息体(Message-Body)

Start-Line

* Message-Header

CRLF

[Message-Body]

消息头结构

SIP协议定义了大量的消息头域,但在一个基本SIP请求中至少应该包含以下几个消息体头域:

From : 请求发起端地址
To : 请求目的端地址
Call-ID : 呼叫标识
Contract :联系人信息
CSeq : 消息序号
Max-Forward :TTL,防止死循环
Via : 消息转发记录
Content-Length : 消息体长度

消息体结构

SIP协议并没有规定消息体的结构,对消息体的应用完全取决于应用自身。

sip协议REGISTER请求格式

01  REGISTER sip:{remote_ip} SIP/2.0
02  Via: {viaInfo}
03  Max-Forwards: 70
04  From: {uacInfo}
05  To: {uasInfo}
06  Call-ID: {call_id}
07  User-Agent: {uaInfo}
08  CSeq: {csnum}
09  Contact: {contractInfo}
10  Expires: {expireSeconds}
11  Authorization: {authInfo}
12  Content-Length: {msgBodyLength}
  • 第1行的REGISTER表示这是一条注册消息
  • 第2行的via表示sip消息的路由
  • 第3行指出消息最多可以转发多少次,防止死循环
  • 第4行至第9行参考前面的消息头结构
  • 第10行说明本次注册的有效期,单位为秒
  • 第11行是sip认证信息,发送第一次sip register 请求时没有该字段
  • 第12行sip消息体的长度,一般为0

注册流程

终端向服务器发送REGISTER请求

sip消息示例:

REGISTER sip:192.168.168.85 SIP/2.0
Via: SIP/2.0/UDP 192.168.168.168:25338;branch=z9hG4bK-d87543-1a71103b47634958-1--d87543-;rport
Max-Forwards: 70
Contact: <sip:1000@192.168.168.168:25338;rinstance=196b0ce810f2e6f5>
To: "1000"<sip:1000@192.168.168.85>
From: "1000"<sip:1000@192.168.168.85>;tag=2d1fbf20
Call-ID: ZTRiYTBhZmVlYTM1ZDkxOWQ3OWNkNjkwMmYxMWI5Yjk.
CSeq: 1 REGISTER
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1010f stamp 39239
Content-Length: 0

服务器回复401,并给出WWW-Authenticate信息

sofia协议栈:
    tport :
        tport_recv_event
            => tport_deliver
    nta :
        agent_recv_request
            => agent_aliases
    nua :
        nua_stack_process_request      

sofia 应用层:
    sofia_reg_handle_sip_i_register
        => sofia_reg_handle_register_token
            => sofia_reg_auth_challenge (回复401)

sip消息示例:

SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.168.168:25338;branch=z9hG4bK-d87543-1a71103b47634958-1--d87543-;rport=25338
From: "1000" <sip:1000@192.168.168.85>;tag=2d1fbf20
To: "1000" <sip:1000@192.168.168.85>;tag=m6ecFK1Fy3F0a
Call-ID: ZTRiYTBhZmVlYTM1ZDkxOWQ3OWNkNjkwMmYxMWI5Yjk.
CSeq: 1 REGISTER
User-Agent: FreeSWITCH-mod_sofia/1.6.8+git~20160505T153832Z~99de0ad502~64bit
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, path, replaces
WWW-Authenticate: Digest realm="192.168.168.85", nonce="d54e4bb9-fc22-4e08-8b69-442e1b8774eb", algorithm=MD5, qop="auth"
Content-Length: 0

终端再次向服务器发送REGISTER请求,并携带认证信息

REGISTER sip:192.168.168.85 SIP/2.0
Via: SIP/2.0/UDP 192.168.168.168:25338;branch=z9hG4bK-d87543-6371fe0e115be271-1--d87543-;rport
Max-Forwards: 70
Contact: <sip:1000@192.168.168.168:25338;rinstance=196b0ce810f2e6f5>
To: "1000"<sip:1000@192.168.168.85>
From: "1000"<sip:1000@192.168.168.85>;tag=2d1fbf20
Call-ID: ZTRiYTBhZmVlYTM1ZDkxOWQ3OWNkNjkwMmYxMWI5Yjk.
CSeq: 2 REGISTER
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1010f stamp 39239
Authorization: Digest username="1000",realm="192.168.168.85",nonce="d54e4bb9-fc22-4e08-8b69-442e1b8774eb",uri="sip:192.168.168.85",response="c46ae8e7eaa2ee63a1d61bf575d8c395",cnonce="71c1997e810fc38b53b97fbb33dc8b1e",nc=00000001,qop=auth,algorithm=MD5
Content-Length: 0

服务器认证后回复200

fs处理过程描述

sofia协议栈:

tport :
    tport_recv_event
        => tport_deliver
nta :
    agent_recv_request
        => agent_aliases
nua :
    nua_stack_process_request

sofia应用层:

sofia_reg_handle_sip_i_register
    => sofia_reg_handle_register_token
        => sofia_reg_parse_auth (执行认证操作) for_the_sake_of_interop标签附近

认证成功后发送200 OK给客户端。

认证算法描述

具体的认证过程可以参考sofia_reg_parse_auth函数,这里描述下sip注册的认证算法。
sip注册认证使用的是www-authenticate认证算法,具体可参考RFC2617文档。下面进行简单的描述

对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值,结果称为 HA1。
对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值,例如,"REGISTER" 和 "sip:192.168.1.80",结果称为 HA2。
对 HA1、服务器密码随机数(nonce)、请求计数(nc)、客户端密码随机数(cnonce)、保护质量(qop)以及 HA2 的合并值计算 MD5 哈希值。结果即为客户端提供的 response 值。 response 值由三步计算而成。当多个数值合并的时候,使用冒号作为分割符。

计算HA1

HA1 = MD5(A1) = MD5(username:realm:password)

计算HA2

如果qop值为"auth"或未指定,那么HA2为

    HA2 = MD5(A2) = MD5(method:digestURI)

如果qop值为"auth-init",那么HA2为

    HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody))

计算response

如果qop值为"auth"或"auth-init",那么response为

    response = MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)

如果qop未知道,那么response为

    response = MD5(HA1:nonce:HA2)

sip注册涉及数据库

  • sip_authentication
  • sip_registrations

注册过程模拟

python模拟注册过程

#! /usr/bin/env python
#-*- coding:utf-8 -*-

# sip reg test
# E-Mail : [email protected]

import sys,socket,time,traceback
import uuid
import hashlib

svrIp,svrPort = "192.168.168.85",5060
transportType = "udp"
localIp,localPort = "192.168.168.168",17061
uid,passwd = "1000","1234"
g_branch,g_callId = uuid.uuid1(),uuid.uuid1()

def getRegHeader(seqNum):
    retStr = "REGISTER sip:{remote_ip} SIP/2.0\r\n"
    retStr += "Via: SIP/2.0/{transport} {local_ip}:{local_port};branch={branch}\r\n"
    retStr += "Max-Forwards: 70\r\n"
    retStr += "From: {uid} <sip:{uid}@{remote_ip}:{remote_port}>;tag={call_number}\r\n"
    retStr += "To: {uid} <sip:{uid}@{remote_ip}:{remote_port}>\r\n"
    retStr += "Call-ID: {call_id}\r\n"
    retStr += "User-Agent: fs testing\r\n"
    retStr += "CSeq: %d REGISTER\r\n"%seqNum
    retStr += "Contact: sip:{uid}@{local_ip}:{local_port}\r\n"
    retStr += "Expires: 3600\r\n"
    return retStr

def formatRegHeader(patternStr):
    retstr = patternStr.format(
        remote_ip=svrIp,
        transport=transportType,
        local_ip=localIp,
        local_port=localPort,
        branch=g_branch,
        uid=uid,
        remote_port=svrPort,
        call_number=uid,
        call_id=g_callId)
    return retstr

class SipRegObj(object):
    def __init__(self,uid,passwd,
        realm="",nonce="",algorithm="MD5",qop="auth"):
        self.uid = uid # "1000"
        self.password = passwd # "1234"
        self.realm = realm
        self.nonce = nonce
        self.algorithm = algorithm
        self.qop = qop
        self.uri = ""
        self.method = "REGISTER"
        self.cnonce = ""
        self.nc = "00000001"
        self.svrIp = svrIp
        #self.transportType = "udp"

    def getReponse(self):
        md5 = hashlib.md5
        ha1 = md5("%s:%s:%s"%(self.uid,self.realm,self.password)).hexdigest()
        ha2 = md5("%s:%s"%(self.method,self.uri)).hexdigest()
        reponse = md5("%s:%s:%s:%s:%s:%s"%(ha1,self.nonce,self.nc,self.cnonce,self.qop,ha2)).hexdigest()
        return reponse

    def getAuthStr(self):
        self.uri,self.cnonce = ("sip:%s"%self.svrIp),uuid.uuid1()
        retStr = ""
        response = self.getReponse()
        retStr += ‘Authorization: Digest username="%s",realm="%s",nonce="%s",uri="%s",response="%s",cnonce="%s",nc=%s,qop=%s,algorithm=%s\r\n‘%(
            self.uid,self.realm,self.nonce,self.uri,response,self.cnonce,self.nc,self.qop,self.algorithm)
        retStr += ‘Content-Length: 0\r\n‘
        return retStr

    def genChallengeMsg(self):
        # gen sip reg challenge message
        str1=getRegHeader(1) + "Content-Length: 0\r\n"
        return formatRegHeader(str1)        

    def genAuthMsg(self,retstr1):
        # parse data
        data1 = retstr1.split("\r\n")[-4].split(": Digest")[1]
        data1 = str(data1).replace(‘MD5‘,‘"MD5"‘)
        tmpList = data1.split(",")
        for item in tmpList :
            #print item
            arrtmp = item.split("=")
            setattr(self,arrtmp[0].strip(),arrtmp[1].strip(‘"‘))
        # gen sip reg message with auth
        str2=getRegHeader(2)
        str2 = formatRegHeader(str2)
        str2 += self.getAuthStr()
        return str2

if __name__ == "__main__":
    treg = SipRegObj(uid,passwd)

    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    client.bind((localIp,localPort))
    dstHost = (svrIp,svrPort)

    client.sendto(treg.genChallengeMsg(),dstHost)
    print time.time(),‘ : send success‘

    # get response (401 msg)
    retstr1 = client.recv(1024)
    print retstr1

    client.sendto(treg.genAuthMsg(retstr1),dstHost)
    print time.time(),‘ : send success‘

    retstr2 = client.recv(1024)
    print "return str : \r\n",retstr2

    time.sleep(30)

本文github地址:

https://github.com/mike-zhang/mikeBlogEssays/blob/master/2016/20160912_freeswitch注册过程分析.md

欢迎补充

时间: 2024-08-06 07:54:10

freeswitch注册过程分析的相关文章

Dalvik虚拟机JNI方法的注册过程分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8923483 在前面一文中,我们分析了Dalvik虚拟机的运行过程.从中可以知道,Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个 JNI方法,那么就会直接跳到它的地址去执行.也就是说,JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行.由此也可看 出,JNI方法是Android应用程序与本地操作系统

JDBC注册过程分析

本博客要记录的是如何去使用JDBC去操作数据库的一般过程. JDBC操作步骤如下: 1.注册驱动 2.建立连接 3.创建语句对象 4.执行语句 5.处理结果集 6.关闭资源 看如下代码: package Base; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import com.mysql.jdbc.Connection; import com.mysql.jdb

dubbo作为消费者注册过程分析

请支持原创: http://www.cnblogs.com/donlianli/p/3847676.html 作者当前分析的版本为2.5.x.作者在分析的时候,都是带着疑问去查看代码,debug进行调试的,笔者写此文章仅供参考. 先大概了解一下系统作为一个消费者从启动到注册完成的过程 系统启动时,引用service时首先将系统本身自己需要引用的服务注册到zookeeper,然后订阅系统需要的服务,最后,会接收到zookeeper发送的订阅信息.比如一个消费者注册了一个UserService,系统

Android Binder机制(2) ContextManager注册过程分析

引言 Context Manager对应的进程为servicemanager,它先于Service Server与服务客户端运行,首先进入接收IPC数据的状态,处理来自Service Server或服务客户端的请求.在init.rc脚本文件中也可以看到Context Manager在mediaserver与system_server之前运行了. 每当Service Server注册服务时,Context Manager都会把服务的名称与Binder节点编号注册到自身的服务目录中,该服务目录通过根

深入理解SpringCloud之Eureka注册过程分析

eureka是一种去中心化的服务治理应用,其显著特点是既可以作为服务端又可以作为服务向自己配置的地址进行注册.那么这篇文章就来探讨一下eureka的注册流程. 一.Eureka的服务端 eureka的服务端核心类是EurekaBootstrap,该类实现了一个ServletContextListener的监听器.因此我们可以断定eureka是基于servlet容器实现的.关键代码如下: public class EurekaBootStrap implements ServletContextL

FreeSWITCH 1.2.5.3 Step by Step Install

Ubuntu: apt-get -y install build-essential automake autoconf git-core wget libtool apt-get -y install libncurses5-dev libtiff-dev libjpeg-dev zlib1g-dev 从 Git 仓库安装: 从代码库安装能让你永远使用最新的版本: git clone git://git.freeswitch.org/freeswitch.git cd freeswitch .

Chromium扩展(Extension)加载过程分析

Chromium在启动的时候,会根据当前用户的Profile创建一个Extension Service.Extension Service在创建过程中,会加载当前已经安装的所有Extension,并且将它们注册在一个Extension Registry中.以后通过这个Extension Registry,就可以得到当前可用的Extension的信息了.本文接下来就分析Extension的加载过程. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! Chrom

freeswitch 使用外部directory 代替磁盘xml文件

大家都知道,FreeSWITCH默认是从磁盘中xml文件,决定系统中有哪些用户的. 对于一个常规的信息系统,用户信息都是存储在数据库中. 以下非常简单的演示了,FreeSWITCH中的用户如何使用数据库注册. 准备工作: 一套LAMP或LNMP环境 编译FreeSWITCH 的mod_xml_curl 模块 Apache + php7 + MySQL 5.7 中的php脚本 根据实际情况,修改相关参数 [email protected]/var/www/html$ cat chunli_test

Dubbo推刊

dubbo源码阅读:rpc请求处理流程(1) 架构设计:系统间通信(17)--服务治理与Dubbo 中篇(分析) 13. Dubbo原理解析-注册中心之Zookeeper协议注册中心 dubbo作为消费者注册过程分析 - 邯郸人 Dubbo源码分析(二):Dubbo之消费端(Consumer)