最近在测试Opensips + Freeswitch的集群方案,仿照的是Freeswitch中的案例。测试中发现,通过Opensips到Freeswitch注册的话机,有些能呼通,有些无法呼通。
Freeswitch中有这么一段提示:

目前我在两台freeswitch实例配置了相同的数据源,唯独不知道如何配置成相同的域。
看网上的解决方案中,是将多台freeswitch的vars.xml中的domain变量配置成相同的值,但是经过测试,发现该方法并未解决跨freeswitch注册的问题。
freeswitch中记录注册信息的表有两张,sip_registrations与registrations,结合freeswitch-cli命令show registrations反查freeswitch中的代码逻辑,代码如下
else if (!strcasecmp(command, "registrations")) {
switch_snprintfv(sql, sizeof(sql), "select * from registrations where hostname='%q'", switch_core_get_switchname());
if (argv[1] && !strcasecmp(argv[1], "count")) {
switch_snprintfv(sql, sizeof(sql), "select count(*) from registrations where hostname='%q'", switch_core_get_switchname());
holder.justcount = 1;
if (argv[2] && argv[3] && !strcasecmp(argv[2], "as")) {
as = argv[3];
}
}
} 即freeswitch获取话机注册信息会将regitrations或sip_registrations的hostname作为检索字段,该字段的值通过switch_core_get_switchname获取,后者返回switchname。
在freeswitch-cli中可以通过switchname命令查询当前实例的switchname
在switch.conf.xml可以指定switchname。若没有配置switchname,则freeswitch或选择主机的hostname,因此通过修改主机的hostname也可以解决跨freeswitch注册话机无法通信的问题。
下面是switch.conf.xml中关于switchname的描述
<!--
Set the Switch Name for HA environments.
When setting the switch name, it will override the system hostname for all DB and CURL requests
allowing cluster environments such as RHCS to have identical FreeSWITCH configurations but run
as different hostnames.
-->
<!-- <param name="switchname" value="freeswitch"/> -->补充
关于freeswitch中sip_profile中的domain,首先让我们看看注释:
<!-- indicator to parse the directory for domains with parse="true" to get gateways-->
<!--<domain name="$${domain}" parse="true"/>-->
<!-- indicator to parse the directory for domains with parse="true" to get gateways and alias every domain to this profile -->
<!--<domain name="all" alias="true" parse="true"/>-->新手往往不明白此处的注释到底是什么意思。实际上,gateway除了在sip_profile中定义外,还可以在directory中的user中定义。domain标签的name属性用来指定directory,parse属性决定是否解析user中定义的网关,alias属性决定是否将该directory的domain作为该profile的别名。
关于user中网关的作用,那是因为freeswitch又实施了一个机制,在呼叫配置了gateway的用户时,会自动通过该gateway呼出。
目前通过源代码层面,说一下到底应该如何配置。
首先,freeswitch用来记录AOR的表有两张,分别是sip_registrations、registrations(我现在非常纳闷freeswitch的开发者是处于何种原因非要设计两张表)。其中sofia_contact时查询的sip_registrations这张表,show registrations查询的是registrations这张表,由于sofia_contact与show registrations的查询条件不一样,因此在某台freeswitch使用show registrations能查询到注册的话机,但并不代表sofia_contact也能查询到。
前面配置switchname只能解决show registrations不能查询到集群中所有的话机的问题。而sofia_contact才是呼叫时需要调用的。
通过理解注册与呼叫过程,有助于理解sip_profile与directory中的各个domian的作用
注册
按照RFC3261标准,registar的aor的信息来自于REGISTER-request中to-header,包括uri-schema、user以及hostport。freeswitch在此处则更加灵活:
- user来自于token <- to-header <- from-header。其中token是freeswitch中acl实施的一种机制,后续中的token也来源于该机制
- hostport来自于force-reg-db-domain <- token <- to-header <- from-header
- username用于鉴权,来自于Authorization,通常freeswitch要求user与user-name一致
- realm来自于Authorization,受sip_profile中challenge-realm影响。
- userdomain来自于force-reg-domain <- realm
- 鉴权时通过username、userdomain到directory中查找对应的user
- 若鉴权通过,则将该user的aor记录到sip_registrations中。其中sip_user=user,sip_host=hostport,sip_username=username,sip_realm=realm
发起呼叫
通常在freeswitch的dialplan中,基本使用user/user-id@user-domain来发起呼叫。例如 bridge user/xxx@yyy
- 通过user-id、user-domain到directory中查找到对应的user
- 通过user的dial_string获取callee的contact
- dialed_user来自于user-id
- dialed_domain来自于user-domain
- 通过sofia_contact从sip_registrations查询sip_user=dialed_user与sip_host=dialed_domain的contact
- 使用查到到的contact,发起INVITE
总结
在opensips + freeswitch集群方案中,若freeswitch作为registrar,
- 通过将集群中的freeswitch的switchname统一配置,这样就可以在任意freeswitch查询到当前所有注册的话机
- 由于话机的to-header中填写的是opensips的地址,因此freeswitch的directory需要配置正确domain,有以下两张方案
- 统一将集群中freeswitch的directory的域都配置成opensips的hostport,且删除sip_profile中的force-reg-domain、force-reg-db-domain、force-sub-domain
- 统一配置集群中freeswitch的sip_profile中的force-reg-domain、force-reg-db-domain、force-sub-domain