FreeSwitch部署

1. 在CentOS7.2中部署freeswitch环境

vmware虚拟机使用网络共享的话没办法让freeswitch监听5060端口,换成桥接之后就恢复正常了

1.1 在signalwire.com生成一个token

1.2 通过signalwire源下载并安装rpm包

1
2
3
4
5
6
7
8
9
# 配置初始参数
echo "signalwire" > /etc/yum/vars/signalwireusername
echo "TOKEN" > /etc/yum/vars/signalwiretoken # 这里需要替换Token字段为刚生成的token

# 安装所需的源
yum install -y https://$(< /etc/yum/vars/signalwireusername):$(< /etc/yum/vars/signalwiretoken)@freeswitch.signalwire.com/repo/yum/centos-release/freeswitch-release-repo-0-1.noarch.rpm epel-release

# 安装freeswitch
yum install -y freeswitch-config-vanilla freeswitch-lang-* freeswitch-sounds-*

1.3 关闭防火墙

反正是内网测试,安全问题并不是第一位的,为了避免不必要的端口问题就关闭防火墙:

1
2
systemctl disable firewalld
systemctl stop firewalld

1.4 启动并检查freeswitch的运行状态

启动freeswitch:

1
freeswitch -nc -nonat # 后台运行且不使用NAT穿透

如果报错没有这个命令的话,使用下面命令安装:

1
yum install net-tools

检查freeswitch是否监听了对应端口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
netstat -npl | grep freeswitch
tcp        0      0 192.168.77.183:5060     0.0.0.0:*               LISTEN      2871/freeswitch
tcp        0      0 192.168.77.183:5066     0.0.0.0:*               LISTEN      2871/freeswitch
tcp        0      0 192.168.77.183:7443     0.0.0.0:*               LISTEN      2871/freeswitch
tcp        0      0 192.168.77.183:5080     0.0.0.0:*               LISTEN      2871/freeswitch
tcp6       0      0 2409:894a:2d:998:2:5060 :::*                    LISTEN      2871/freeswitch
tcp6       0      0 :::8021                 :::*                    LISTEN      2871/freeswitch
tcp6       0      0 2409:894a:2d:998:2:5080 :::*                    LISTEN      2871/freeswitch
udp        0      0 192.168.77.183:5060     0.0.0.0:*                           2871/freeswitch
udp        0      0 192.168.77.183:5080     0.0.0.0:*                           2871/freeswitch
udp6       0      0 2409:894a:2d:998:2:5060 :::*                                2871/freeswitch
udp6       0      0 2409:894a:2d:998:2:5080 :::*                                2871/freeswitch

可以看到freeswitch正常启动且能够监听几个常用端口

1.5 systemd相关问题

通过rpm包安装的freeswitch会自动注册systemd服务,可以使用systemctl命令控制freeswitch的启停。

现在直接用systemctl status freeswitch会出现这两个报错:

1
2
3
4
5
6
7
8
9
● freeswitch.service - FreeSWITCH
   Loaded: loaded (/usr/lib/systemd/system/freeswitch.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2023-08-10 17:49:14 CST; 2min 57s ago
 Main PID: 962 (freeswitch)
   CGroup: /system.slice/freeswitch.service
           └─962 /usr/bin/freeswitch -nc -nf
Aug 10 17:49:14 localhost.localdomain systemd[1]: Started FreeSWITCH.
Aug 10 17:49:14 localhost.localdomain freeswitch[962]: ERROR: Failed to set SCHED_FIFO scheduler (Operation not permitted)
Aug 10 17:49:14 localhost.localdomain freeswitch[962]: ERROR: Could not set nice level

权限问题,通过修改/usr/lib/systemd/system/freeswitch.service可以修复问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[Unit]
Description=FreeSWITCH
After=syslog.target network.target
After=postgresql.service postgresql-9.3.service postgresql-9.4.service mysqld.service httpd.service

[Service]
EnvironmentFile=-/etc/sysconfig/freeswitch
Environment="USER=freeswitch"
# RuntimeDirectory is not yet supported in CentOS 7. A workaround is to use /etc/tmpfiles.d/freeswitch.conf
#RuntimeDirectory=/run/freeswitch
#RuntimeDirectoryMode=0750
WorkingDirectory=/run/freeswitch
ExecStart=/usr/bin/freeswitch -nc -nf -u ${USER} $FREESWITCH_PARAMS
ExecReload=/usr/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

然后重新加载一下systemctl:

1
systemctl daemon-reload

现在再重新测试就恢复正常了:

1
2
3
4
5
6
7
8
● freeswitch.service - FreeSWITCH
   Loaded: loaded (/usr/lib/systemd/system/freeswitch.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2023-08-10 18:00:29 CST; 15s ago
 Main PID: 948 (freeswitch)
   CGroup: /system.slice/freeswitch.service
           └─948 /usr/bin/freeswitch -nc -nf -u freeswitch

Aug 10 18:00:29 localhost.localdomain systemd[1]: Started FreeSWITCH.

最后记得禁用一下开机自启,方便之后调试:

1
systemctl disable freeswitch

1.6 语音测试&其中的坑

  1. 手机和iPad下载 linphone

  2. 两个账户为1000(手机)和1010(iPad),密码均为1234,地址为192.168.77.183

测试了两个项目:

  1. 1000(手机)拨打9198,能听到Tetris的BGM,且能正常通话
  2. 1000(手机)拨打1010(iPad),语音拨打有三十秒主动断连的问题:

1.7 填坑

https://blog.csdn.net/FlyLikeButterfly/article/details/100581609?ydreferer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8%3D

这个更像是原因说明,我的实验场景是在一个小内网中的,在默认情况下通话建立后,设备一直再向公网地址发送ack,相当于虚空喊话,自然得不到另一台设备的应答,于是经过设置的默认30秒超时时间之后就自动断连了。

修改方法如下,也可以直接将这两行注释掉。

1
2
3
4
5
vim /etc/freeswitch/sip_profiles/internal.xml

# 修改这两行
    <param name="ext-rtp-ip" value="$${local_ip_v4}"/>
    <param name="ext-sip-ip" value="$${local_ip_v4}"/>

然后重新检测freeswitch运行状态:

1
kill -9 $(pidof freeswitch) && freeswitch -nc -nonat

经测试,修复了三十秒自动挂断的问题。

1.8 其他

经过测试,现在是可以正常进行视频和音频通话的,但是有两个比较头疼的问题:

  1. 拨号等待时间过久;
  2. 通话延迟比较高,音频通话可能还好一点,视频通话延迟很高,有些影响视频通话的效果。

1.9 单节点双freeswitch桥接

1.9.1复制所有所需文件

由于我这个是直接使用yum从官方源中安装的程序包,其实并不像书里那样文件全都位于/usr/local/freeswitch 中:

1
2
[root@localhost ~]# whereis freeswitch
freeswitch: /usr/bin/freeswitch /usr/lib64/freeswitch /etc/freeswitch /usr/share/freeswitch

四个项目分别是:

  1. 二进制可执行文件
  2. 运行库
  3. 配置文件
  4. 文档、字体、脚本、声音等素材文件

使用下面的命令复制一份:

1
2
3
4
5
6
7
8
cp /usr/bin/freeswitch /usr/bin/freeswitch2

ln -sf /usr/lib64/freeswitch /usr/lib64/freeswitch2

mkdir /etc/freeswitch2
cp -r /etc/freeswitch/* /etc/freeswitch2

ln -sf /usr/share/freeswitch /usr/share/freeswitch2

除此之外还需要知道日志路径、数据库路径等信息

fs_cli中可以使用eval $${variable}查询

1
2
3
4
freeswitch@localhost.localdomain> eval $${db_dir}
/var/lib/freeswitch/db
freeswitch@localhost.localdomain> eval $${log_dir}
/var/log/freeswitch

1.9.2 修改freeswitch2的信息

按照书中的说明修改配置文件:

/etc/freeswitch2/autoload_configs/event_socket.conf.xml

1
<param name="listen-port" value="38021"/>

/etc/freeswitch2/vars.xml

1
2
3
4
  <X-PRE-PROCESS cmd="set" data="internal_sip_port=35060"/>
  <X-PRE-PROCESS cmd="set" data="internal_tls_port=35061"/>
  <X-PRE-PROCESS cmd="set" data="external_sip_port=35080"/>
  <X-PRE-PROCESS cmd="set" data="external_tls_port=35081"/>

这个是书上没有说的,但是如果不配置的话我这边会出现一些报错信息 /etc/freeswitch2/sip_profiles/internal.xml

1
2
    <param name="ws-binding"  value=":35066"/>
    <param name="wss-binding" value=":37443"/>

1.9.3 运行并检测状态

所以freeswitch2的启动命令就是:

1
freeswitch2 -conf /etc/freeswitch2 -log /var/log/freeswitch2 -db /var/lib/freeswitch2/db -nc -nonat

检查运行状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@localhost ~]# netstat -npl | grep freeswitch
tcp        0      0 192.168.109.218:7443    0.0.0.0:*               LISTEN      2807/freeswitch
tcp        0      0 192.168.109.218:37443   0.0.0.0:*               LISTEN      2812/freeswitch2
tcp        0      0 192.168.109.218:5060    0.0.0.0:*               LISTEN      2807/freeswitch
tcp        0      0 192.168.109.218:35060   0.0.0.0:*               LISTEN      2812/freeswitch2
tcp        0      0 192.168.109.218:5080    0.0.0.0:*               LISTEN      2807/freeswitch
tcp        0      0 192.168.109.218:35080   0.0.0.0:*               LISTEN      2812/freeswitch2
tcp        0      0 192.168.109.218:5066    0.0.0.0:*               LISTEN      2807/freeswitch
tcp        0      0 192.168.109.218:35066   0.0.0.0:*               LISTEN      2812/freeswitch2
tcp6       0      0 2409:894a:22:228c::5060 :::*                    LISTEN      2807/freeswitch
tcp6       0      0 2409:894a:22:228c:35060 :::*                    LISTEN      2812/freeswitch2
tcp6       0      0 2409:894a:22:228c::5080 :::*                    LISTEN      2807/freeswitch
tcp6       0      0 2409:894a:22:228c:35080 :::*                    LISTEN      2812/freeswitch2
tcp6       0      0 :::8021                 :::*                    LISTEN      2807/freeswitch
tcp6       0      0 :::38021                :::*                    LISTEN      2812/freeswitch2
udp        0      0 192.168.109.218:5060    0.0.0.0:*                           2807/freeswitch
udp        0      0 192.168.109.218:35060   0.0.0.0:*                           2812/freeswitch2
udp        0      0 192.168.109.218:5080    0.0.0.0:*                           2807/freeswitch
udp        0      0 192.168.109.218:35080   0.0.0.0:*                           2812/freeswitch2
udp6       0      0 2409:894a:22:228c::5060 :::*                                2807/freeswitch
udp6       0      0 2409:894a:22:228c:35060 :::*                                2812/freeswitch2
udp6       0      0 2409:894a:22:228c::5080 :::*                                2807/freeswitch
udp6       0      0 2409:894a:22:228c:35080 :::*                                2812/freeswitch2

1.9.4 两个FSM的桥接

在两个freeswitch的配置路径/etc/freeswitch/中找到./dialplan/default.xml,添加如下内容:

1
2
3
4
5
6
# vim /etc/freeswitch/dialplan/default.xml
    <extension name="local_bridge">
      <condition field="destination_number" expression="^0(.*)$">
	    <action application="bridge" data="sofia/external/sip:$1@$${local_ip_v4}:35060"/>
      </condition>
    </extension>
1
2
3
4
5
6
# vim /etc/freeswitch2/dialplan/default.xml
    <extension name="local_bridge">
      <condition field="destination_number" expression="^0(.*)$">
	    <action application="bridge" data="sofia/external/sip:$1@$${local_ip_v4}:5060"/>
      </condition>
    </extension>

分别把符合正则表达式的号码桥接到sofia上,sofia再将这个号码转发到对应地址。

2 相关概念

2.1 Profile

类似一个分流策略,根据不同的规则(可能是IP、可能是载荷类型,总之有多种分类方式)将入站流量进行分流。

FreeSwitch默认提供了internalexternal两个Profile,按照官方的说法,internal的适用范围是本地网络中使用FreeSwitch注册的设备,而external适用于其他网络中其他sip中继服务的设备。

对一个channel来说,存在new-init-routing-execute-hangup-reporting-destory这几个流程。

https://blog.51cto.com/u_14349334/3490017

一个profile会启动一个UserAgent

2.2 DialPlan

一个DialPlan可能会由多个Context构成,一个Context下又有多个Extension,这些项目构成了一个树状结构,不同Context下的Extension是相互隔离的。

一个Extension中定义了“条件”和“动作”,也就是这么两个字段。中较为常见的是词条是"destination_number",但也可以使用别的变量作为词条。

这玩意还不能嵌套,只能分情况将多个中的进行逻辑与,同时可以通过:

  1. on-true
  2. on-false
  3. always
  4. never

四种break参数控制判断流向。

除了break参数外,满足的项目会到中进行下一步操作,同时可以使用让不满足的项目执行特定操作。

条件判断完全结束之后才会进行动作,因此在一些条件判断-赋值的场景下会出现错误,通过内联执行的方法能够解决这些问题,但是内联执行本身是一种改变程序自然执行状态的操作,因此会有一些限制。

正则表达式中使用了“()”,因此匹配结果会放入变量$1中。

常见的dialPlan有以下几项:

  1. set
  2. echo
  3. info
  4. answer
  5. bridge
  6. playback
  7. sleep
  8. ring_ready
  9. pre_answer
  10. read
  11. play_and_get_digits

2.2.1 一个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<extension name="Local_Extension">
		//匹配1000-1019,保存到$1中
		<condition field="destination_number" expression="^(10[01][0-9])$"> 
    	// 将号码保存到dialed_extension中
			<action application="export" data="dialed_extension=$1"/>
			<!-- bind_meta_app can have these args <key> [a|b|ab] [a|b|o|s] <app> -->
      // 绑定DTMF
			<action application="bind_meta_app" data="1 b s execute_extension::dx XML features"/>
			<action application="bind_meta_app" data="2 b s record_session::$${recordings_dir}/${caller_id_number}.${strftime(%Y-%m-%d-%H-%M-%S)}.wav"/>
			<action application="bind_meta_app" data="3 b s execute_extension::cf XML features"/>
			<action application="bind_meta_app" data="4 b s execute_extension::att_xfer XML features"/>
      // 设置回铃音
			<action application="set" data="ringback=${us-ring}"/>
      // 设置转接时的回铃音
			<action application="set" data="transfer_ringback=$${hold_music}"/>
      //设置超时时间
			<action application="set" data="call_timeout=30"/>
			<!-- <action application="set" data="sip_exclude_contact=${network_addr}"/> -->
			<action application="set" data="hangup_after_bridge=true"/>
			<!--<action application="set" data="continue_on_fail=NORMAL_TEMPORARY_FAILURE,USER_BUSY,NO_ANSWER,TIMEOUT,NO_ROUTE_DESTINATION"/> -->
			<action application="set" data="continue_on_fail=true"/>
      // 设置哈希,绑定在腿上
			<action application="hash" data="insert/${domain_name}-call_return/${dialed_extension}/${caller_id_number}"/>
			<action application="hash" data="insert/${domain_name}-last_dial_ext/${dialed_extension}/${uuid}"/>
			<action application="set" data="called_party_callgroup=${user_data(${dialed_extension}@${domain_name} var callgroup)}"/>
			<action application="hash" data="insert/${domain_name}-last_dial_ext/${called_party_callgroup}/${uuid}"/>
			<action application="hash" data="insert/${domain_name}-last_dial_ext/global/${uuid}"/>
			<!--<action application="export" data="nolocal:rtp_secure_media=${user_data(${dialed_extension}@${domain_name} var rtp_secure_media)}"/>-->
			<action application="hash" data="insert/${domain_name}-last_dial/${called_party_callgroup}/${uuid}"/>
			<action application="bridge" data="user/${dialed_extension}@${domain_name}"/>
			<action application="answer"/>
			<action application="sleep" data="1000"/>
			<action application="bridge" data="loopback/app=voicemail:default ${domain_name} ${dialed_extension}"/>
		</condition>
</extension>

满足条件中设置的正则表达式的时候就会进入动作层,在默认配置里一般是执行,这些application大部分在mode_dptools中定义:

https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Modules/mod_dptools_1970333/

单腿、多腿呼叫:一个用户(User Agent)和FreeSwitch之间的连接被称为一个“leg”或“channel”,因此单腿呼叫常见场景就是ivr,即用户和FreeSwitch通话。双腿呼叫也就是两个用户通过FreeSwitch建立连接。