呼叫中心平台中坐席是不可或缺的一环,而坐席打电话自然需要使用办公分机。通常情况下我们通过软交换平台FreeSWITCH、Asterisk即可搭建分机注册服务。
但单台FreeSWITCH或Asterisk难以承载高并发的注册服务,而且从服务模块化的角度,我们也希望将注册服务和媒体服务相分离,所以我们通常会是使用OpenSIPS 或 Kamailio 来搭建注册服务器。
今天就让我们一同来看一下,如何通过OpenSIPS搭建一个简单的分机注册服务器吧……
 
目录:
  1. 业务场景
  2. 运行环境
  3. 关键模块
  4. 涉及的数据库表
  5. 分机注册信令细节
  6. 注册的认证过程
  7. auth_db模块变量说明
  8. 个性化功能
    • 禁止单个分机账户多地注册
  9. 注册脚步详情
  10. 术语解释
  11. 测试方法
 
1. 业务场景:
  • OpenSIPS为分机提供注册服务,分机可以经过OpenSIPS进行互打
    
 
2. 运行环境:
   CentOS 7.4
   OpenSIPS 2.4.2
 
3. 使用的关键模块:
    SIP signaling modules  :   registrar、signaling、sl
    Auth modules             :    auth、auth_db
    Data caching              :   usrloc
 
4. 涉及的数据库表:
    subscriber   : 存放分机号、密码等信息
    location       : 已注册的分机信息
 
5. 分机注册信令细节(RFC3261):
 
    
  • 分机注册、取消注册都是使用 SIP REGISTER 方法,只是取消注册的时候,消息头中的过期时长expires的值是 0
  • 分机注册需要进行认证
    • 注册服务器返回 401/407 来要求终端发起认证
    • 认证不通过,则注册服务器返回 403 (Forbidden)
    • 认证过程中,注册服务器找不到AOR (Address-of-Record),则返回404 (Not Found)
  • 分机注册的(建议)过期时长可以依次从下面两个地方获取 :
    • Contact 头中的 expires 参数
    • Expires  头
    • 以上两项都没有,则由注册服务器指定一个默认值 (如OpenSIPS的registrar模块配置"default_expires=120)
  • 注册成功后的实际过期时长是由注册服务器来决定,并在注册服务器返回的200 OK中的Contact里通过携带 'expires'参数来告知分机终端注册信息实际的有效时长:
    • 终端REGISTER请求中的expires可能不在注册服务器允许expires范围内,注册服务器会强制使用自己指定的expires值
    • 比如REGISTER中的Expires=20,  而OpenSIPS配置的min_expires=30,那么OpenSIPS返回的200 OK中的expires值是30 (如:Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf>;expires=30)
  • 分机终端需要周期性发送保活注册包
    • 保活过程中,每次重发注册请求,CSeq 值自增 1
    • 保活注册时,Call-ID始终不变
    • 保活注册包发送周期:在达到注册服务器返回的200 OK中的expires时长之前,重发保活注册请求 (如Yealink会在expires/2时长后重新注册,而Zoiper、EyeBeam会在注册过期前5秒重新注册)
  • 如果注册的响应报文包含Date 消息头,那么SIP终端需要通过该值来保持跟注册服务器的时间同步,以确保注册周期的准确性

 下面报文中,
===> 首次发起注册
REGISTER sip:10.2.84.19 SIP/2.0
Via: SIP/2.0/UDP 10.32.26.19:;branch=z9hG4bK-d87543-c66bc353296ef71c---d87543-;rport
Max-Forwards:
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>
To: ""<sip:@10.2.84.19>
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
Expires: 过期时间为 20秒
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1011d stamp
Content-Length: ===>未认证,要求认证,携带附加信息WWW-Authenticate:
SIP/2.0 Unauthorized
Via: SIP/2.0/UDP 10.32.26.19:;received=10.32.26.19;branch=z9hG4bK-d87543-c66bc353296ef71c---d87543-;rport=
To: ""<sip:@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.a5a8
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER 【CSeq 跟前一个一样】
WWW-Authenticate: Digest realm="10.2.84.19", nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537"
Server: OpenSIPS (2.4. (x86_64/linux))
Content-Length: ===> 携带认证信息(Authorization)后,再次重发注册消息
REGISTER sip:10.2.84.19 SIP/2.0
Via: SIP/2.0/UDP 10.32.26.19:;branch=z9hG4bK-d87543-f26ee314e330e316---d87543-;rport
Max-Forwards:
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>
To: ""<sip:@10.2.84.19>
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER 【CSeq 跟前一个一样】
Expires:
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1011d stamp
Authorization: Digest username="",realm="10.2.84.19",nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537",uri="sip:10.2.84.19",response="9c46fa8d95df62b2d204c82bf4fcdccc",algorithm=MD5
Content-Length: ===> 首次注册成功
你可能注意到, OK 中 Contact 里的 expires=, 不是终端请求是设置的
因为我OpenSIPS的registrar模块配置的最小过期时长是30
====
SIP/2.0 OK
Via: SIP/2.0/UDP 10.32.26.19:;received=10.32.26.19;branch=z9hG4bK-d87543-f26ee314e330e316---d87543-;rport=
To: ""<sip:@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.fdb8
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>;expires=
Server: OpenSIPS (2.4. (x86_64/linux))
Content-Length: ===> 15秒后再次发起注册【因为SIP终端是 EyeBeam,它是在过期前5秒重新发起注册】,注意,这次重发时,没有返回401
REGISTER sip:10.2.84.19 SIP/2.0
Via: SIP/2.0/UDP 10.32.26.19:;branch=z9hG4bK-d87543-83347a2a0b242e5c---d87543-;rport
Max-Forwards:
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>
To: ""<sip:@10.2.84.19>
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER 【CSeq 跟前一个一样】
Expires:
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1011d stamp
Authorization: Digest username="",realm="10.2.84.19",nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537",uri="sip:10.2.84.19",response="9c46fa8d95df62b2d204c82bf4fcdccc",algorithm=MD5
Content-Length: SIP/2.0 OK
Via: SIP/2.0/UDP 10.32.26.19:;received=10.32.26.19;branch=z9hG4bK-d87543-83347a2a0b242e5c---d87543-;rport=
To: ""<sip:@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.f41b
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>;expires=
Server: OpenSIPS (2.4. (x86_64/linux))
Content-Length: ===> 15秒后再次发起注册,注意,这次重发时,返回401,要求采用最新的 nonce值认证
REGISTER sip:10.2.84.19 SIP/2.0
Via: SIP/2.0/UDP 10.32.26.19:;branch=z9hG4bK-d87543-1e206865de74ec34---d87543-;rport
Max-Forwards:
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>
To: ""<sip:@10.2.84.19>
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
Expires:
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1011d stamp
Authorization: Digest username="",realm="10.2.84.19",nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537",uri="sip:10.2.84.19",response="9c46fa8d95df62b2d204c82bf4fcdccc",algorithm=MD5
Content-Length: ===> 返回401,WWW-Authenticate 中 nonce 的值发生改变
SIP/2.0 Unauthorized
Via: SIP/2.0/UDP 10.32.26.19:;received=10.32.26.19;branch=z9hG4bK-d87543-1e206865de74ec34---d87543-;rport=
To: ""<sip:@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.79d6
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
WWW-Authenticate: Digest realm="10.2.84.19", nonce="5eb611acb7d0e927969146dcaf0ce1777070df6e", stale=true
Server: OpenSIPS (2.4. (x86_64/linux))
Content-Length: ===> 使用新的 nonce 值再次发起注册,并且返回成功
REGISTER sip:10.2.84.19 SIP/2.0
Via: SIP/2.0/UDP 10.32.26.19:;branch=z9hG4bK-d87543-7f61e57ce525c45d---d87543-;rport
Max-Forwards:
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>
To: ""<sip:@10.2.84.19>
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
Expires:
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1011d stamp
Authorization: Digest username="",realm="10.2.84.19",nonce="5eb611acb7d0e927969146dcaf0ce1777070df6e",uri="sip:10.2.84.19",response="1f1a4b29257e292a5efc686ea846245d",algorithm=MD5
Content-Length: SIP/2.0 OK
Via: SIP/2.0/UDP 10.32.26.19:;received=10.32.26.19;branch=z9hG4bK-d87543-7f61e57ce525c45d---d87543-;rport=
To: ""<sip:@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.771a
From: ""<sip:@10.2.84.19>;tag=704c1b46
Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ.
CSeq: REGISTER
Contact: <sip:@10.32.26.19:;rinstance=870ce245f5361eaf>;expires=
Server: OpenSIPS (2.4. (x86_64/linux))
Content-Length: ===> 取消注册的SIP报文
REGISTER sip:10.2.84.19: SIP/2.0
Via: SIP/2.0/UDP 10.34.26.140:;branch=z9hG4bK3803710069
From: "" <sip:@10.2.84.19:>;tag=
To: "" <sip:@10.2.84.19:>
Call-ID: 1_213524545@10.34.26.140
CSeq: REGISTER
Contact: <sip:@10.34.26.140:>
Authorization: Digest username="", realm="10.2.84.19", nonce="5eb503f9b40c6d1f396eef36c55a6713b8421c74", uri="sip:10.2.84.19:5060", response="c5e2ee46cba287df8f434fe631a2f3fd", algorithm=MD5
Allow: INVITE, INFO, PRACK, ACK, BYE, CANCEL, OPTIONS, NOTIFY, REGISTER, SUBSCRIBE, REFER, PUBLISH, UPDATE, MESSAGE
Max-Forwards:
User-Agent: Yealink SIP-T21P_E2 52.80.0.147
Expires:
Allow-Events: talk,hold,conference,refer,check-sync
Content-Length:
6. 注册的认证过程:
     
     SIP 是采用HTTP摘要方式(Digest)进行认证。
  1. 认证:调用auth_db模块的www_authorize("", "subscriber")进行分机身份认证,根据认证情况继续
    • 返回 -3 (stale nonce)、-4 (no credentials),则调用 auth 模块的 www_challenge("","0")要求分机携带认证凭证再次注册。该方法会返回SIP 401,并且消息头中有一个 WWW-Authenticate 头,等SIP终端再次发送注册请求时就会在SIP INVITE 内携带 Authorization 头信息
    • 返回 -1 (invalid user),则调用signaling模块send_reply("404", "Not Found")
    • 返回 -2 (invalid password)、-5 (generic error),则调用signaling模块send_reply("403", "Forbidden")
    • 返回 1 (success),进行下一步
  2. 存储注册信息:调用registrar模块的save("location", "f") 将注册信息写入location表
    • 写入DB失败,则调用 sl 模块的 sl_reply_error() 返回错误信息
    www_authorize(realm, table_name) : 
        realm : 所注册分机所属的域,通常是域名或IP,该值并不一定是subscriber中的domain字段,具体根据auth_db里的参数配置决定。 (回401时,该参数是强制的,在所有的盘问中都必须有。它是目的是鉴别SIP消息中的机密。在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名)
                  通常该值是REG服务器的IP,如果realm填空串"",对于REGISTER注册请求而言,会从To头取domain/ip地址(相当于$td), 而其他请求则从From头中取(相当于$fd)。
                  该值也可以使用伪变量指定。如 $rd  、$td 、$fd
        table_name : 认证信息存储的表名称,如subscriber。
 
    www_challenge(realm, qop_enable) :
        realm : 同上所属
        qop_enable : 
              是否双向认证。可选值 0、1, 当qop_enable=1时,后续步骤中的 WWW-Authenticate 和 Authorization  步骤得值都会携带qop参数,401会有nonce, 再次发送的 REGISTER会有cnonce
              如:qop_enable=1时,返回401消息:WWW-Authenticate: Digest realm="10.2.32.112", nonce="5f05c370000000090661a6e56d5451e0ba81b4883dc37bca", qop="auth", stale=true
              再次发送的REGISTER消息:Authorization: Digest username="9001",realm="10.2.32.112",nonce="5f05c370000000090661a6e56d5451e0ba81b4883dc37bca",uri="sip:10.2.32.112;transport=tcp",response="d5792a2d89b0c158c1c9453f98385cb8",cnonce="06681fd4664cda35938959789ed84849",nc=00000001,qop=auth,algorithm=MD5【response是加密后的密码, 其他参数的解释请参见https://www.cnblogs.com/shengs/p/4361314.html 或RFC2617 HTTP认证】
 
   除了身份认证,OpenSIPS还可以使用下面几个方法完成代理认证:
  • proxy_authorize(realm, table_name)
  • proxy_challenge(realm, qop_enable)    [返回407,消息头 Proxy-Authenticate、Proxy-Authorization ]
  • consume_credentials()
 
7. auth_db模块变量说明:
  • db_url                          : (string) 数据库访问路径,如果不设置,则采用db_default_url
  • user_column                : (string) 数据库subscriber表中用于存放UA的用户名信息的列名,默认是username
  • domain_column           : (string) 数据库subscriber表中存放UA所属的租户信息的列名,默认是domain
  • password_column        : (string) OpenSIPS运行时,使用subscriber表中作为UA密码的列名,默认是 ha1
    • 根据username、password、realm(非表中的domain)计算出的 MD5 hash值
    • 使用 MI命令 opensipsctl add username password 会自动生成 ha1的值
    • 注意,如果calculate_ha1=1,必须设置password_column="password",用于存储明文密码;calculate_ha1=0 时,该值可不设或者设置成ha1
  • password_column_2     : (string) OpenSIPS运行时,使用subscriber表中作为UA密码的列名,默认是 ha1b
    • 同ha1, 但计算hash的方式不同,根据 username@domain、password、realm 计算出的MD5 hash值
    • 只有当calculate_ha1=0,并且UA发起的注册请求中username携带domain信息时有用,如username=9001@10.2.32.112 [$fu=sip:9001@010.2.32.112@10.2.32.112;transport=UDP]
  • calculate_ha1                : (int) OpenSIPS处理REGISTER请求时,是否需要重新计算密码的HASH值,取值分别如下:
    • 1 :OpenSIPS会从加载明文密码,然后计算MD5 hash值,所以password_column的值必须=password
    • 0 :(默认值)不计算密码,如果注册请求中username不带domain时,则直接从ha1中取密码进行验证,否则取ha1b的值【此时不能设置成password_column="password"】
  • user_domain                  : (int) 是否支持多租户,取值范围 [0,1] ,默认值 0。
    • MI命令 opensipsctl add username password 不支持添加多租户的UA信息
    • 所以需要自己想办法为不同租户初始化subscriber表中的 ha1和ha1b,当然你不设置ha1和ha1b,OpenSIPS根据明文计算的方式解决(模块参数password_column="password"、calculate_ha1=1
 
    subscriber表样例:
        
        username : 用户名(分机号)
        domain : 租户名称[IP、域名、或者是自己随意定义的名称],当你想支持多租户配置时有用(modparam("auth_db", "use_domain", 1)),注册请求From头样例: "9999"<sip:9999@tony.com>       
        从上面样例中可以看到 分机号为 9999  的 ha1 和 ha1b 的值为空,因为我采用根据明文密码计算MD5 hash值得方式模块参数password_column="password"、calculate_ha1=1
 
8. 个性化功能:
 
   1、不允许单个分机账号被拥有不同IP的多个终端同时注册(但同一IP下,单个账户可以同时用多个SIP终端注册),并且单个终端注册成功后,可以重发注册请求,以便更新过期时间
    有以下两种方案,其中方案A性能更好。  方案B 更灵活。
 
     实现方案A:【从OpenSIPS内存中获取分机登录状态】
  • 通过 registar 模块的 is_ip_registered、is_registered方法检测当前注册分机是否已经被注册过,如果已经被注册,则直接返回返回403 , 提示账户被占用 Occupied
  • 如果同时满足下面几个条件,则不允许再注册:(存在已经注册的分机,但IP不是自己)
  • (1) is_ip_registered 根据 $tu 分机号(username) 和 $si (分机终端IP) 检测到分机未注册 【未注册:返回 -1】
  • (2) is_registered 检测到$tu 分机号(username) 已经注册 【已注册:返回 1】
  • 其中第一步如果返回1,表示已注册,则需放行因分机注册信息过期而再次发起的注册请求,以便更新过期时间【因为根据终端IP判断,所以不会影响端断电重启后再次注册】
  • [备注:registar 模块还有 is_contact_registered方法,可以根据分机号+callid判断分机注册情况,如 is_contact_registered("location", "$tu", , "$ci") ]

 # 此处省略分机认证过程
$var(pA) = is_ip_registered("location", "$tu", "$si");
# check current callid not registed
if ( $var(pA) < ) {
xlog("SIP contact ct:[$ct] [$tu] ci:[$ci] did not registe on, then check registe status for tU:[$tU]."); if ( is_registered("location") ) { # if current caller has registed by another UA
xlog("L_WARN", "Forbid $ct to registe on, cuase by : exist another $fU has registed");
send_reply("", "Occupied");
exit;
}
} if (!save("location", "f")) { #将注册信息写入DB
sl_reply_error();
}
 
     实现方案B:【从DB获取分机登录状态】
  • 通过avpops模块的 avp_db_query 直接查询数据库的 location 表来实现功能

 # 此处省略分机认证过程
avp_db_query("select count(*) from location where username = '$fU' and contact like 'sip:$fU@$si%'", "$avp(existExtenCount)");
if ( $avp(existExtenCount) < ) {
xlog("SIP contact ct:[$ct] [$tu] ci:[$ci] did not registe on, then check registe status for tU:[$tU]."); avp_db_query("select count(*) from location where username = '$fU'", "$avp(ct4fU)");
if ( $avp(ct4fU) > ) {
xlog("L_WARN", "Forbid $ct to registe on, cuase by : exist another $fU has registed");
send_reply("", "Occupied");
exit;
}
} if (!save("location", "f")) { #将注册信息写入DB
sl_reply_error();
}
 
9. 注册脚步详情:
  • 通过下面脚步,注册几个分机后,就能完成分机互打了
  • 添加分机的方式 : /usr/local/OpenSIPS/sbin/OpenSIPSctl add ${分机号} ${密码}
    • /usr/local/OpenSIPS/sbin/OpenSIPSctl add 9001 9001
  • 添加的分机会写入 subscriber 表, 注册信息会写入 location表

 log_level=
log_stderror=no
log_facility=LOG_LOCAL0 children=
auto_aliases=no listen=udp:192.168.1.201: # CUSTOMIZE ME ####### Modules Section ######## mpath="/usr/local/OpenSIPS242//lib64/OpenSIPS/modules/" db_default_url="mysql://root:passwd@192.168.1.202:3306/OpenSIPS242" loadmodule "proto_udp.so"
loadmodule "db_mysql.so"
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "maxfwd.so"
loadmodule "sipmsgops.so" loadmodule "rr.so"
modparam("rr", "append_fromtag", ) loadmodule "uri.so"
modparam("uri", "use_uri_table", ) loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/OpenSIPS_fifo")
modparam("mi_fifo", "fifo_mode", ) loadmodule "tm.so"
modparam("tm", "fr_timeout", )
modparam("tm", "fr_inv_timeout", )
modparam("tm", "restart_fr_on_each_reply", )
modparam("tm", "onreply_avp_mode", ) loadmodule "auth.so"
loadmodule "auth_db.so"
#需要根据明文密码重新计算MD5 hash值
modparam("auth_db", "calculate_ha1", )
#是否支持多租户:subscriber表中支持 username相同,但domain字段不同。注册时,会查domain
modparam("auth_db", "use_domain", )
#从明文密码列取密码
modparam("auth_db", "password_column", "password") loadmodule "usrloc.so"
modparam("usrloc", "db_mode", )
modparam("usrloc", "nat_bflag", "NAT_BFLAG")
#modparam("usrloc", "working_mode_preset", "single-instance-sql-write-through")
#是否支持多租户
modparam("usrloc", "use_domain", )
modparam("usrloc", "hash_size", ) #### REGISTRAR module
loadmodule "registrar.so"
modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT")
modparam("registrar", "max_contacts", )
modparam("registrar", "min_expires", )
modparam("registrar", "max_expires", )
modparam("registrar", "default_expires", ) ####### Routing Logic ######## # main request routing logic route{ xlog("receive message method[$rm] fU[$fU] tU[$tU] | ci[$ci] si[$si] sp[$sp] rd[$rd] rU[$rU] tu[$tu] fd[$fd] ct[$ct]"); if (!mf_process_maxfwd_header("")) {
send_reply("","Too Many Hops");
exit;
} if (is_method("OPTIONS")){
t_reply("", "OK");
exit;
} $var(pTg) = has_totag();
xlog("method[$rm] has_totag = $var(pTg)"); if ( $var(pTg) > ) { if (loose_route()) {
xlog("loose_route ---> as receive message method[$rm]");
# route it out to whatever destination was set by loose_route() in $du (destination URI).
route(relay);
} else {
if ( is_method("ACK") ) {
if ( t_check_trans() ) {
# non loose-route, but stateful ACK; must be an ACK after a or e.g. from upstream server
xlog("receive message method[$rm] then do t_relay");
t_relay();
exit;
} else {
xlog("receive message method[$rm] no transaction , then exit");
exit;
}
}
sl_send_reply("","Not here");
}
exit;
} # CANCEL processing
if (is_method("CANCEL")) {
if (t_check_trans())
t_relay();
exit;
} # absorb retransmissions, but do not create transaction
t_check_trans(); # record routing
if (!is_method("REGISTER|MESSAGE")) {
record_route();
} # requests for my domain
if (is_method("PUBLISH|SUBSCRIBE")) {
t_reply("", "Method Not Allowed ");
exit;
} if (is_method("REGISTER")) {
xlog("rhjiang---test--Do REGISTER AUTH : [$rm] ci[$ci] si[$si] sp[$sp] rd[$rd] rU[$rU] fU[$fU]");
route(AUTH);
} if ($rU==NULL) {
# request with no Username in RURI
send_reply("","Address Incomplete");
exit;
} # do lookup with method filtering
if (!lookup("location","m")) {
t_reply("", "Not Found");
exit;
} route(relay);
} route[relay] {
# for INVITEs enable some additional helper routes
if (is_method("INVITE")) {
t_on_branch("per_branch_ops");
t_on_reply("handle_reply");
t_on_failure("missed_call");
} if (!t_relay()) {
send_reply("","Internal Error");
}
exit;
} branch_route[per_branch_ops] {
xlog("new branch at $ru\n");
} onreply_route[handle_reply] {
xlog("incoming reply [$ci] [$si:$sp] [$rs]\n");
} failure_route[missed_call] {
if (t_was_cancelled()) {
exit;
}
} route[AUTH]{ $var(authRslt) = www_authorize("$td", "subscriber"); # the same as --> www_authorize("", "subscriber");
xlog("register auth result [$var(authRslt)] rd [$rd] user[$fU] source ip[$si]");
switch ($var(authRslt)) {
case -:
send_reply("", "Not Found");
exit;
case -:
case -:
send_reply("", "Forbidden");
exit;
case -:
case -:
###www_challenge("$td","");
www_challenge("$td","");
exit;
} $var(pA) = is_ip_registered("location", "$tu", "$si");
if ( $var(pA) < ) {
xlog("====SIP contact ct:[$ct] [$fu] ci:[$ci] did not registe on, then check registe status for fU:[$fU]."); if ( is_registered("location")) {
xlog("L_WARN", "====Forbid $ct to registe on, cuase by : exist another $fU has registed");
send_reply("", "Occupied");
exit;
}
} if (!save("location", "f")) {
sl_reply_error();
} $var(expire) = "";
if ( $hdrcnt(Expires) == ) {
$var(expire) = $ct.fields(expires);
xlog("no expire header ,contact expire is [$var(expire)]");
} else {
$var(expire) = $hdr(Expires);
xlog("expires header , expire [$var(expire)]");
} exit;
}
 
10. 术语解释:
  • Contact  : SIP终端账户的具体物理IP和端口,用于描述我在哪里。一个注册请求中可以有多个Contact消息头。SIP终端之间可以通过Contact中的信息实现点对点通信。
  • AOR (Address of Record)  : SIP终端账户的唯一标识,用于描述我是谁,From 和 To 头中就是 AOR地址,格式为 SIP:username@domain_host 或者 SIP:username@ip 。 AOR地址是可以解析得到Contact地址。
 
11. 测试方法:
   启动OpenSIPS : 
        /usr/local/OpenSIPS/sbin/OpenSIPS -f /usr/local/OpenSIPS/etc/OpenSIPS/OpenSIPS.cfg -P /var/run/opensips.pid -m 4096 -M 384 -u root -g root
   可以通过 /usr/local/OpenSIPS/sbin/OpenSIPSctl ul show [分机号(可选)] 查看内存中的分机的注册状态
[root@yuxiu home]# /usr/local/OpenSIPS/sbin/OpenSIPSctl ul show
AOR::
Contact:: sip:@192.168.1.201: Q=
ContactID::
Expires::
Callid:: 1_636487152@192.168.1.201
Cseq::
User-agent:: SIP-T21P_E2 52.80.0.147
State:: CS_SYNC
Flags::
Cflags::
Socket:: udp:192.168.1.218:
Methods::

相关网址:
  https://tools.ietf.org/html/rfc3261#section-10
  https://www.opensips.org/Documentation/Tutorials-MidRegistrar
  http://www.kamailio.org.cn/doku.php?id=OpenSIPS_cfg_mysql%E5%88%86%E6%9C%BA%E6%B3%A8%E5%86%8C 【分机注册】
  https://mjd507.github.io/2018/01/25/HTTP-Authorization/ 【HTTP认证方式】

 
 
 
 
    

基于OpenSIPS 实现分机注册服务服务器的更多相关文章

  1. 基于OpenSIPS做注册服务下,场景A打B,一方发起BYE挂断后收到500,另一方无法挂断的问题

    基于OpenSIPS做注册服务下,场景A打B,一方发起BYE挂断后收到500,另一方无法挂断的问题     最近在工作中遇到一个看似很奇怪的,排除起来很费劲,但最后的解决方式又及其简单的问题,下面我们 ...

  2. 基于 Consul 实现 MagicOnion(GRpc) 服务注册与发现

    0.简介 0.1 什么是 Consul Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置. 这里所谓的服务,不仅仅包括常用的 Api 这些服务,也包括软件开发过程 ...

  3. arcgis连接oracle发布服务,提示数据未注册到服务器,手动注册服务器失败

    arcgis连接oracle数据库发布服务时候,分析之后提示:数据未注册到服务器上. 手动注册之后提示:数据客户端没有正确配置.实际上数据库客户端已经安装完成也可以使用. 设置 PATH 环境变量(仅 ...

  4. 一台服务器发布多个tomcat并注册服务名办法

    修改服务名称 打开Tomcat7.0.65_1/bin/service.bat  修改注册服务名称 当然这个名字自己改 比如Tomcat7_2 原始文件:   set SERVICE_NAME=Tom ...

  5. 手把手教你写一个windows服务 【基于.net】 附实用小工具{注册服务/开启服务/停止服务/删除服务}

    1,本文适用范围 语言:.net 服务类型:windows服务,隔一段时间执行 2,服务搭建: 1,在vs中创建 console程序 2,在console项目所在类库右键 添加-新建项-选择Windo ...

  6. 基于Dubbo框架构建分布式服务(一)

    Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...

  7. 基于开源Dubbo分布式RPC服务框架的部署整合

    一.前言 Dubbo 作为SOA服务化治理方案的核心框架,用于提高业务逻辑的复用.整合.集中管理,具有极高的可靠性(HA)和伸缩性,被应用于阿里巴巴各成员站点,同时在包括JD.当当在内的众多互联网项目 ...

  8. maven小项目注册服务(一)--email和persist模块

    跟着书里的讲解,跟着做了一遍该项目: 首先明白注册账户的需求: 账号的lD和Email地址都可以用来唯一地标识某个用户,而显示名称则用来显示在页面下,方便浏览.注册的时候用户还需要输入两次密码,以确保 ...

  9. 基于Dubbo框架构建分布式服务

    Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...

随机推荐

  1. SW算法求全局最小割(Stoer-Wagner算法)

    我找到的唯一能看懂的题解:[ZZ]最小割集Stoer-Wagner算法 似乎是一个冷门算法,连oi-wiki上都没有,不过洛谷上竟然有它的模板题,并且2017百度之星的资格赛还考到了.于是来学习一下. ...

  2. 印象笔记如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器?

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接  印象笔记如何使用二次验证码/虚拟MFA/两步验证/谷歌验证器? 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载a ...

  3. .net core options 依赖注入的方式

    options 依赖注入的方式 public class JwtSettingsOptions { public const string JwtSettings = "JwtSetting ...

  4. asp.net core appsetting.json 绑定读取

    appsettings.json中,具有: "AppSettings": { "AzureConnectionKey": "***", &q ...

  5. Jenkins怎么安装?Jenkins控制台输出乱码怎么处理?Jenkins执行selenium脚本时浏览器不显示怎么处理?

    今天我们来看一看Jenkins的安装. 首先我们看一下Jenkins是什么,能够干什么.Jenkins呢是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开 ...

  6. web自动化 -- ActionChains()的鼠标操作

    webdriver模块下的ActionChains类 一.两个主要组件 1.实例化  ActionChains() 2.ActionChains(driver).perform() perform() ...

  7. python学习之路------你想要的都在这里了

    python学习之路------你想要的都在这里了 (根据自己的学习进度后期不断更新哟!!!) 一.python基础 1.python基础--python基本知识.七大数据类型等 2.python基础 ...

  8. PHP xml_parser_get_option() 函数

    定义和用法 xml_parser_get_option() 函数从 XML 解析器获取选项.高佣联盟 www.cgewang.com 如果成功,该函数则返回选项值.如果失败,则返回 FALSE 和一个 ...

  9. Python语言及其应用 中文pdf完整版|网盘下载内附提取码

    点击此处下载提取码:7cvm <Python语言及其应用>介绍Python 语言的基础知识及其在各个领域的具体应用,基于新版本3.x.书中首先介绍了Python 语言的一些基本知识,然后介 ...

  10. 5.4 省选模拟赛 修改 线段树优化dp 线段树上二分

    LINK:修改 题面就不放了 大致说一下做法.不愧是dls出的题 以前没见过这种类型的 不过还是自己dp的时候写丑了. 从这道题中得到一个结论 dp方程要写的优美一点 不过写的过丑 优化都优化不了. ...