freeswitch注册过程分析
操作系統(tǒng):debian8.5_x64
freeswitch 版本 : 1.6.8
本文僅描述sip注冊的簡單場景,即話機直接向處于同一個局域網(wǎng)的fs進行注冊。
SIP協(xié)議的消息結(jié)構(gòu)
消息框架
SIP協(xié)議是基于文本的協(xié)議,SIP協(xié)議的消息都遵從一個統(tǒng)一的消息結(jié)構(gòu): 起始行(Start-Line)、一個或多個頭域(Message-Header)、表明域結(jié)束的空行(CRLF),以及 可選的消息體(Message-Body)
Start-Line * Message-Header CRLF [Message-Body]
消息頭結(jié)構(gòu)
SIP協(xié)議定義了大量的消息頭域,但在一個基本SIP請求中至少應該包含以下幾個消息體頭域:
From : 請求發(fā)起端地址
To : 請求目的端地址
Call-ID : 呼叫標識
Contract :聯(lián)系人信息
CSeq : 消息序號
Max-Forward :TTL,防止死循環(huán)
Via : 消息轉(zhuǎn)發(fā)記錄
Content-Length : 消息體長度
消息體結(jié)構(gòu)
SIP協(xié)議并沒有規(guī)定消息體的結(jié)構(gòu),對消息體的應用完全取決于應用自身。
sip協(xié)議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行指出消息最多可以轉(zhuǎn)發(fā)多少次,防止死循環(huán)
第4行至第9行參考前面的消息頭結(jié)構(gòu)
第10行說明本次注冊的有效期,單位為秒
第11行是sip認證信息,發(fā)送第一次sip register 請求時沒有該字段
第12行sip消息體的長度,一般為0
注冊流程
終端向服務器發(fā)送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協(xié)議棧:
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
終端再次向服務器發(fā)送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協(xié)議棧:
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 (執(zhí)行認證操作) for_the_sake_of_interop標簽附近
認證成功后發(fā)送200 OK給客戶端。
認證算法描述
具體的認證過程可以參考sofia_reg_parse_auth函數(shù),這里描述下sip注冊的認證算法。
sip注冊認證使用的是www-authenticate認證算法,具體可參考RFC2617文檔。下面進行簡單的描述
對用戶名、認證域(realm)以及密碼的合并值計算 MD5 哈希值,結(jié)果稱為 HA1。
對HTTP方法以及URI的摘要的合并值計算 MD5 哈希值,例如,"REGISTER" 和 "sip:192.168.1.80",結(jié)果稱為 HA2。
對 HA1、服務器密碼隨機數(shù)(nonce)、請求計數(shù)(nc)、客戶端密碼隨機數(shù)(cnonce)、保護質(zhì)量(qop)以及 HA2 的合并值計算 MD5 哈希值。結(jié)果即為客戶端提供的 response 值。 response 值由三步計算而成。當多個數(shù)值合并的時候,使用冒號作為分割符。
計算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注冊涉及數(shù)據(jù)庫
sip_authentication
sip_registrations
注冊過程模擬
python模擬注冊過程
#! /usr/bin/env python
#-*- coding:utf-8 -*-
# sip reg test
# E-Mail : Mike_Zhang@live.com
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
"
retStr += "Via: SIP/2.0/{transport} {local_ip}:{local_port};branch={branch}
"
retStr += "Max-Forwards: 70
"
retStr += "From: {uid} <sip:{uid}@{remote_ip}:{remote_port}>;tag={call_number}
"
retStr += "To: {uid} <sip:{uid}@{remote_ip}:{remote_port}>
"
retStr += "Call-ID: {call_id}
"
retStr += "User-Agent: fs testing
"
retStr += "CSeq: %d REGISTER
"%seqNum
retStr += "Contact: sip:{uid}@{local_ip}:{local_port}
"
retStr += "Expires: 3600
"
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
'%(
self.uid,self.realm,self.nonce,self.uri,response,self.cnonce,self.nc,self.qop,self.algorithm)
retStr += 'Content-Length: 0
'
return retStr
def genChallengeMsg(self):
# gen sip reg challenge message
str1=getRegHeader(1) + "Content-Length: 0
"
return formatRegHeader(str1)
def genAuthMsg(self,retstr1):
# parse data
data1 = retstr1.split("
")[-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 :
",retstr2
time.sleep(30)
總結(jié)
以上是生活随笔為你收集整理的freeswitch注册过程分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Coverage analysis in
- 下一篇: Flash怎么制作互动的橡皮刷