基于OpenSIPS 实现分机注册服务器【非集群】
呼叫中心平台中坐席是不可或缺的一环,而坐席打电话自然需要使用办公分机。通常情况下我们通过软交换平台FreeSWITCH、Asterisk即可搭建分机注册服务。
但单台FreeSWITCH或Asterisk难以承载高并发的注册服务,而且从服务模块化的角度,我们也希望将注册服务和媒体服务相分离,所以我们通常会是使用OpenSIPS 或 Kamailio 来搭建注册服务器。
今天就让我们一同来看一下,如何通过OpenSIPS搭建一个简单的分机注册服务器吧……
目录:
- 业务场景
- 运行环境
- 关键模块
- 涉及的数据库表
- 分机注册信令细节
- 注册的认证过程
- auth_db模块变量说明
- 个性化功能
- 禁止单个分机账户多地注册
- 注册脚步详情
- 术语解释
- 测试方法
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终端需要通过该值来保持跟注册服务器的时间同步,以确保注册周期的准确性
1 下面报文中,401999 2 ===> 首次发起注册 3 REGISTER sip:10.2.84.19 SIP/2.0 4 Via: SIP/2.0/UDP 10.32.26.19:56862;branch=z9hG4bK-d87543-c66bc353296ef71c-1--d87543-;rport 5 Max-Forwards: 70 6 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf> 7 To: "401999"<sip:401999@10.2.84.19> 8 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 9 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 10 CSeq: 1 REGISTER 11 Expires: 20 过期时间为 20秒 12 Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO 13 User-Agent: eyeBeam release 1011d stamp 40820 14 Content-Length: 0 15 16 ===>未认证,要求认证,携带附加信息WWW-Authenticate: 17 SIP/2.0 401 Unauthorized 18 Via: SIP/2.0/UDP 10.32.26.19:56862;received=10.32.26.19;branch=z9hG4bK-d87543-c66bc353296ef71c-1--d87543-;rport=56862 19 To: "401999"<sip:401999@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.a5a8 20 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 21 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 22 CSeq: 1 REGISTER 【CSeq 跟前一个一样】 23 WWW-Authenticate: Digest realm="10.2.84.19", nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537" 24 Server: OpenSIPS (2.4.2 (x86_64/linux)) 25 Content-Length: 0 26 27 ===> 携带认证信息(Authorization)后,再次重发注册消息 28 REGISTER sip:10.2.84.19 SIP/2.0 29 Via: SIP/2.0/UDP 10.32.26.19:56862;branch=z9hG4bK-d87543-f26ee314e330e316-1--d87543-;rport 30 Max-Forwards: 70 31 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf> 32 To: "401999"<sip:401999@10.2.84.19> 33 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 34 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 35 CSeq: 2 REGISTER 【CSeq 跟前一个一样】 36 Expires: 20 37 Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO 38 User-Agent: eyeBeam release 1011d stamp 40820 39 Authorization: Digest username="401999",realm="10.2.84.19",nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537",uri="sip:10.2.84.19",response="9c46fa8d95df62b2d204c82bf4fcdccc",algorithm=MD5 40 Content-Length: 0 41 42 ===> 首次注册成功 43 你可能注意到,200 OK 中 Contact 里的 expires=30, 不是终端请求是设置的 20 44 因为我OpenSIPS的registrar模块配置的最小过期时长是30 45 ==== 46 SIP/2.0 200 OK 47 Via: SIP/2.0/UDP 10.32.26.19:56862;received=10.32.26.19;branch=z9hG4bK-d87543-f26ee314e330e316-1--d87543-;rport=56862 48 To: "401999"<sip:401999@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.fdb8 49 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 50 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 51 CSeq: 2 REGISTER 52 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf>;expires=30 53 Server: OpenSIPS (2.4.2 (x86_64/linux)) 54 Content-Length: 0 55 56 ===> 15秒后再次发起注册【因为SIP终端是 EyeBeam,它是在过期前5秒重新发起注册】,注意,这次重发时,没有返回401 57 REGISTER sip:10.2.84.19 SIP/2.0 58 Via: SIP/2.0/UDP 10.32.26.19:56862;branch=z9hG4bK-d87543-83347a2a0b242e5c-1--d87543-;rport 59 Max-Forwards: 70 60 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf> 61 To: "401999"<sip:401999@10.2.84.19> 62 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 63 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 64 CSeq: 3 REGISTER 【CSeq 跟前一个一样】 65 Expires: 20 66 Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO 67 User-Agent: eyeBeam release 1011d stamp 40820 68 Authorization: Digest username="401999",realm="10.2.84.19",nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537",uri="sip:10.2.84.19",response="9c46fa8d95df62b2d204c82bf4fcdccc",algorithm=MD5 69 Content-Length: 0 70 71 SIP/2.0 200 OK 72 Via: SIP/2.0/UDP 10.32.26.19:56862;received=10.32.26.19;branch=z9hG4bK-d87543-83347a2a0b242e5c-1--d87543-;rport=56862 73 To: "401999"<sip:401999@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.f41b 74 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 75 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 76 CSeq: 3 REGISTER 77 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf>;expires=30 78 Server: OpenSIPS (2.4.2 (x86_64/linux)) 79 Content-Length: 0 80 81 ===> 15秒后再次发起注册,注意,这次重发时,返回401,要求采用最新的 nonce值认证 82 REGISTER sip:10.2.84.19 SIP/2.0 83 Via: SIP/2.0/UDP 10.32.26.19:56862;branch=z9hG4bK-d87543-1e206865de74ec34-1--d87543-;rport 84 Max-Forwards: 70 85 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf> 86 To: "401999"<sip:401999@10.2.84.19> 87 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 88 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 89 CSeq: 4 REGISTER 90 Expires: 20 91 Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO 92 User-Agent: eyeBeam release 1011d stamp 40820 93 Authorization: Digest username="401999",realm="10.2.84.19",nonce="5eb6117930f436d5c6dfab18f8b2da91e65c1537",uri="sip:10.2.84.19",response="9c46fa8d95df62b2d204c82bf4fcdccc",algorithm=MD5 94 Content-Length: 0 95 96 ===> 返回401,WWW-Authenticate 中 nonce 的值发生改变 97 SIP/2.0 401 Unauthorized 98 Via: SIP/2.0/UDP 10.32.26.19:56862;received=10.32.26.19;branch=z9hG4bK-d87543-1e206865de74ec34-1--d87543-;rport=56862 99 To: "401999"<sip:401999@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.79d6 100 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 101 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 102 CSeq: 4 REGISTER 103 WWW-Authenticate: Digest realm="10.2.84.19", nonce="5eb611acb7d0e927969146dcaf0ce1777070df6e", stale=true 104 Server: OpenSIPS (2.4.2 (x86_64/linux)) 105 Content-Length: 0 106 107 ===> 使用新的 nonce 值再次发起注册,并且返回成功 108 REGISTER sip:10.2.84.19 SIP/2.0 109 Via: SIP/2.0/UDP 10.32.26.19:56862;branch=z9hG4bK-d87543-7f61e57ce525c45d-1--d87543-;rport 110 Max-Forwards: 70 111 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf> 112 To: "401999"<sip:401999@10.2.84.19> 113 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 114 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 115 CSeq: 5 REGISTER 116 Expires: 20 117 Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO 118 User-Agent: eyeBeam release 1011d stamp 40820 119 Authorization: Digest username="401999",realm="10.2.84.19",nonce="5eb611acb7d0e927969146dcaf0ce1777070df6e",uri="sip:10.2.84.19",response="1f1a4b29257e292a5efc686ea846245d",algorithm=MD5 120 Content-Length: 0 121 122 SIP/2.0 200 OK 123 Via: SIP/2.0/UDP 10.32.26.19:56862;received=10.32.26.19;branch=z9hG4bK-d87543-7f61e57ce525c45d-1--d87543-;rport=56862 124 To: "401999"<sip:401999@10.2.84.19>;tag=0903184bf06bf229be5b2f19c45648e4.771a 125 From: "401999"<sip:401999@10.2.84.19>;tag=704c1b46 126 Call-ID: YjEwZDFhZDJmY2Y0MDg4ZDQ1YjJhMTU0MGFlYTE0YmQ. 127 CSeq: 5 REGISTER 128 Contact: <sip:401999@10.32.26.19:56862;rinstance=870ce245f5361eaf>;expires=30 129 Server: OpenSIPS (2.4.2 (x86_64/linux)) 130 Content-Length: 0 131 132 ===> 401998 取消注册的SIP报文 133 REGISTER sip:10.2.84.19:5060 SIP/2.0 134 Via: SIP/2.0/UDP 10.34.26.140:5060;branch=z9hG4bK3803710069 135 From: "401998" <sip:401998@10.2.84.19:5060>;tag=2259286663 136 To: "401998" <sip:401998@10.2.84.19:5060> 137 Call-ID: 1_213524545@10.34.26.140 138 CSeq: 3 REGISTER 139 Contact: <sip:401998@10.34.26.140:5060> 140 Authorization: Digest username="401998", realm="10.2.84.19", nonce="5eb503f9b40c6d1f396eef36c55a6713b8421c74", uri="sip:10.2.84.19:5060", response="c5e2ee46cba287df8f434fe631a2f3fd", algorithm=MD5 141 Allow: INVITE, INFO, PRACK, ACK, BYE, CANCEL, OPTIONS, NOTIFY, REGISTER, SUBSCRIBE, REFER, PUBLISH, UPDATE, MESSAGE 142 Max-Forwards: 70 143 User-Agent: Yealink SIP-T21P_E2 52.80.0.147 144 Expires: 0 145 Allow-Events: talk,hold,conference,refer,check-sync 146 Content-Length: 0
6. 注册的认证过程:
SIP 是采用HTTP摘要方式(Digest)进行认证。
- 认证:
- 调用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),进行下一步
- 存储注册信息:
- 调用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") ]
1 # 此处省略分机认证过程 2 $var(pA) = is_ip_registered("location", "$tu", "$si"); 3 # check current callid not registed 4 if ( $var(pA) < 0 ) { 5 xlog("SIP contact ct:[$ct] [$tu] ci:[$ci] did not registe on, then check registe status for tU:[$tU]."); 6 7 if ( is_registered("location") ) { # if current caller has registed by another UA 8 xlog("L_WARN", "Forbid $ct to registe on, cuase by : exist another $fU has registed"); 9 send_reply("403", "Occupied"); 10 exit; 11 } 12 } 13 14 if (!save("location", "f")) { #将注册信息写入DB 15 sl_reply_error(); 16 }
实现方案B:【从DB获取分机登录状态】
- 通过avpops模块的 avp_db_query 直接查询数据库的 location 表来实现功能
1 # 此处省略分机认证过程 2 avp_db_query("select count(*) from location where username = '$fU' and contact like 'sip:$fU@$si%'", "$avp(existExtenCount)"); 3 if ( $avp(existExtenCount) < 1 ) { 4 xlog("SIP contact ct:[$ct] [$tu] ci:[$ci] did not registe on, then check registe status for tU:[$tU]."); 5 6 avp_db_query("select count(*) from location where username = '$fU'", "$avp(ct4fU)"); 7 if ( $avp(ct4fU) > 0) { 8 xlog("L_WARN", "Forbid $ct to registe on, cuase by : exist another $fU has registed"); 9 send_reply("403", "Occupied"); 10 exit; 11 } 12 } 13 14 if (!save("location", "f")) { #将注册信息写入DB 15 sl_reply_error(); 16 }
9. 注册脚步详情:
- 通过下面脚步,注册几个分机后,就能完成分机互打了
- 添加分机的方式 : /usr/local/OpenSIPS/sbin/OpenSIPSctl add ${分机号} ${密码}
- /usr/local/OpenSIPS/sbin/OpenSIPSctl add 9001 9001
- 添加的分机会写入 subscriber 表, 注册信息会写入 location表
1 log_level=3 2 log_stderror=no 3 log_facility=LOG_LOCAL0 4 5 children=4 6 auto_aliases=no 7 8 listen=udp:192.168.1.201:5060 # CUSTOMIZE ME 9 10 ####### Modules Section ######## 11 12 mpath="/usr/local/OpenSIPS242//lib64/OpenSIPS/modules/" 13 14 db_default_url="mysql://root:passwd@192.168.1.202:3306/OpenSIPS242" 15 16 loadmodule "proto_udp.so" 17 loadmodule "db_mysql.so" 18 loadmodule "signaling.so" 19 loadmodule "sl.so" 20 loadmodule "maxfwd.so" 21 loadmodule "sipmsgops.so" 22 23 loadmodule "rr.so" 24 modparam("rr", "append_fromtag", 0) 25 26 loadmodule "uri.so" 27 modparam("uri", "use_uri_table", 0) 28 29 loadmodule "mi_fifo.so" 30 modparam("mi_fifo", "fifo_name", "/tmp/OpenSIPS_fifo") 31 modparam("mi_fifo", "fifo_mode", 0666) 32 33 loadmodule "tm.so" 34 modparam("tm", "fr_timeout", 5) 35 modparam("tm", "fr_inv_timeout", 30) 36 modparam("tm", "restart_fr_on_each_reply", 0) 37 modparam("tm", "onreply_avp_mode", 1) 38 39 loadmodule "auth.so" 40 loadmodule "auth_db.so" 41 #需要根据明文密码重新计算MD5 hash值 42 modparam("auth_db", "calculate_ha1", 1) 43 #是否支持多租户:subscriber表中支持 username相同,但domain字段不同。注册时,会查domain 44 modparam("auth_db", "use_domain", 1) 45 #从明文密码列取密码 46 modparam("auth_db", "password_column", "password") 47 48 loadmodule "usrloc.so" 49 modparam("usrloc", "db_mode", 1) 50 modparam("usrloc", "nat_bflag", "NAT_BFLAG") 51 #modparam("usrloc", "working_mode_preset", "single-instance-sql-write-through") 52 #是否支持多租户 53 modparam("usrloc", "use_domain", 1) 54 modparam("usrloc", "hash_size", 16) 55 56 #### REGISTRAR module 57 loadmodule "registrar.so" 58 modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT") 59 modparam("registrar", "max_contacts", 1) 60 modparam("registrar", "min_expires", 30) 61 modparam("registrar", "max_expires", 300) 62 modparam("registrar", "default_expires", 120) 63 64 ####### Routing Logic ######## 65 66 # main request routing logic 67 68 route{ 69 70 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]"); 71 72 if (!mf_process_maxfwd_header("10")) { 73 send_reply("483","Too Many Hops"); 74 exit; 75 } 76 77 if (is_method("OPTIONS")){ 78 t_reply("200", "OK"); 79 exit; 80 } 81 82 $var(pTg) = has_totag(); 83 xlog("method[$rm] has_totag = $var(pTg)"); 84 85 if ( $var(pTg) > 0) { 86 87 if (loose_route()) { 88 xlog("loose_route ---> as receive message method[$rm]"); 89 # route it out to whatever destination was set by loose_route() in $du (destination URI). 90 route(relay); 91 } else { 92 if ( is_method("ACK") ) { 93 if ( t_check_trans() ) { 94 # non loose-route, but stateful ACK; must be an ACK after a 487 or e.g. 404 from upstream server 95 xlog("receive message method[$rm] then do t_relay"); 96 t_relay(); 97 exit; 98 } else { 99 xlog("receive message method[$rm] no transaction , then exit"); 100 exit; 101 } 102 } 103 sl_send_reply("404","Not here"); 104 } 105 exit; 106 } 107 108 # CANCEL processing 109 if (is_method("CANCEL")) { 110 if (t_check_trans()) 111 t_relay(); 112 exit; 113 } 114 115 # absorb retransmissions, but do not create transaction 116 t_check_trans(); 117 118 # record routing 119 if (!is_method("REGISTER|MESSAGE")) { 120 record_route(); 121 } 122 123 # requests for my domain 124 if (is_method("PUBLISH|SUBSCRIBE")) { 125 t_reply("405", "Method Not Allowed "); 126 exit; 127 } 128 129 if (is_method("REGISTER")) { 130 xlog("rhjiang---test--Do REGISTER AUTH : [$rm] ci[$ci] si[$si] sp[$sp] rd[$rd] rU[$rU] fU[$fU]"); 131 route(AUTH); 132 } 133 134 if ($rU==NULL) { 135 # request with no Username in RURI 136 send_reply("484","Address Incomplete"); 137 exit; 138 } 139 140 # do lookup with method filtering 141 if (!lookup("location","m")) { 142 t_reply("404", "Not Found"); 143 exit; 144 } 145 146 route(relay); 147 } 148 149 150 route[relay] { 151 # for INVITEs enable some additional helper routes 152 if (is_method("INVITE")) { 153 t_on_branch("per_branch_ops"); 154 t_on_reply("handle_reply"); 155 t_on_failure("missed_call"); 156 } 157 158 if (!t_relay()) { 159 send_reply("500","Internal Error"); 160 } 161 exit; 162 } 163 164 165 branch_route[per_branch_ops] { 166 xlog("new branch at $ru\n"); 167 } 168 169 170 onreply_route[handle_reply] { 171 xlog("incoming reply [$ci] [$si:$sp] [$rs]\n"); 172 } 173 174 failure_route[missed_call] { 175 if (t_was_cancelled()) { 176 exit; 177 } 178 } 179 180 181 route[AUTH]{ 182 183 $var(authRslt) = www_authorize("$td", "subscriber"); # the same as --> www_authorize("", "subscriber"); 184 xlog("register auth result [$var(authRslt)] rd [$rd] user[$fU] source ip[$si]"); 185 switch ($var(authRslt)) { 186 case -1: 187 send_reply("404", "Not Found"); 188 exit; 189 case -2: 190 case -5: 191 send_reply("403", "Forbidden"); 192 exit; 193 case -3: 194 case -4: 195 ###www_challenge("$td","1"); 196 www_challenge("$td","0"); 197 exit; 198 } 199 200 $var(pA) = is_ip_registered("location", "$tu", "$si"); 201 if ( $var(pA) < 0 ) { 202 xlog("====SIP contact ct:[$ct] [$fu] ci:[$ci] did not registe on, then check registe status for fU:[$fU]."); 203 204 if ( is_registered("location")) { 205 xlog("L_WARN", "====Forbid $ct to registe on, cuase by : exist another $fU has registed"); 206 send_reply("403", "Occupied"); 207 exit; 208 } 209 } 210 211 if (!save("location", "f")) { 212 sl_reply_error(); 213 } 214 215 $var(expire) = "0"; 216 if ( $hdrcnt(Expires) == 0 ) { 217 $var(expire) = $ct.fields(expires); 218 xlog("no expire header ,contact expire is [$var(expire)]"); 219 } else { 220 $var(expire) = $hdr(Expires); 221 xlog("expires header , expire [$var(expire)]"); 222 } 223 224 exit; 225 }
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 9001 AOR:: 9001 Contact:: sip:9001@192.168.1.201:5060 Q= ContactID:: 1767885864954038130 Expires:: 163 Callid:: 1_636487152@192.168.1.201 Cseq:: 96 User-agent:: SIP-T21P_E2 52.80.0.147 State:: CS_SYNC Flags:: 0 Cflags:: Socket:: udp:192.168.1.218:5060 Methods:: 16383
相关网址:
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认证方式】