1、簡介
使用go語言編寫一個程序,模擬SIP-T58終端在Freeswitch上注冊用戶
2、思路
- 以客戶端向服務端Freeswitch發起REGISTER請求,告知服務器當前的聯系地址
- 構造SIP REGISTER請求
- 創建UDP連接,連接到Freeswitch的5060端口
- 發送初始的REGISTER請求,不帶認證
- 接收服務器的響應,如果是401(不帶認證),那么需要提取nonce并重新發送帶有認證(Authorization)的REGISTER請求
- 處理服務器返回的響應,確認注冊成功
3、實現過程
- 發送初始的REGISTER請求,此時沒有認證頭,服務器將會返回401,并且帶上WWW-Authenticate頭字段
- 解析401響應,獲取nonce,重新構造REGISTER請求,包含Authorization頭,包含response的摘要
response = MD5( MD5(A1) ":" nonce ":" MD5(A2) )
其中A1是 username:realm:password,A2是 METHOD:uri。
例如,假設用戶1000的密碼是1234,realm是FreeSWITCH,那么A1就是1000:FreeSWITCH:1234,取其MD5哈希,然后與nonce以及A2的MD5哈希(對于REGISTER方法,A2是REGISTER:sip:192.168.0.31)結合,再進行一次MD5計算。
接下來,構建Authorization頭部:
Authorization: Digest username="1000", realm="FreeSWITCH", nonce="...", uri="sip:192.168.0.0", response="...", algorithm=MD5
- 創建UDP套接字,綁定到到本機地址
- 生成必要的SIP頭部字段,客戶端生成唯一的Call-ID,并且CSeq序號每次遞增
- 處理網絡通信,發送和接收UDP數據
- 解析服務器的響應,提取nonce等信息
- 計算response摘要,構造第二個REGISTER請求
4、如何解析服務器的響應
WWW-Authenticate: Digest realm="FreeSWITCH", nonce="abcd1234", algorithm=MD5
需要從該頭中提取realm和nonce的值。
處理這些字符串可能需要字符串分割和提取,或者使用正則表達式。
5、定義變量
freeswitchIP := 192.168.0.0
sipUser := "1000"
password := "123456"
localPort := 5061
創建UDP連接
serverAddr,err := net.ResolveUDPAddr("udp",freeswitchIP+":5061")
if err != nil{
? log.Fatal(err)
}
系統分配本機地址
localAddr,err := net.ResolveUDPAddr("udp",localhostIP+":0")
conn,err := net.DialUDP("udp",localAddr,serverAddr)
if err != nil{
? ? ? ? log.Fatal(err)
}
defer conn.Close()
生成Call-ID,CSeq,branch
callID := generateCallID()
cseq := 1
branch := generateBranch()//z9hG4bK + 隨機字符
生成初始的REGISTER請求
registerRequest := fmt.Sprintf(
"REGISTER sip:%s SIP/2.0\r\n"+
"Via:SIP/2.0/UDP %s:%d;branch=%s\r\n"+
"Max-Forwards:70\r\n"+
"From:<sip:%s@%s>;tag=%s\r\n"+
"To:<sip:%s@%s>\r\n"+
"Call-ID:%s\r\n"+
"CSeq:%d REGISTER"+
"Contact:<sip:%s@%s:%d>\r\n"+
"Expires:%d\r\n"+
"Context-Length:0\r\n\r\n",
freeswitchIP,localhostIP,localAddr.Port,branch,
sipUser,FreeswitchIP,generateTag(),
sipUser,freeswitchIP,
CallID,
cseq,
sipUser,localIP,localPort,
3600,
)