模拟SIP终端向Freeswitch注册用户
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,
)