准备
硬件准备
- centos7.x服务器
- 公网IP(非必须,有的话更好,测试不影响)
- 域名(非必须,有的话更好,测试不影响)
- 证书(非必须,有的话更好,测试不影响)
客户端
网页端
开始安装
yum install -y https://files.freeswitch.org/repo/yum/centos-release/freeswitch-release-repo-0-1.noarch.rpm epel-release
yum install -y freeswitch-config-vanilla freeswitch-lang-* freeswitch-sounds-*
安装速度跟你的网络有关,耐心等待,看到complete,就证明你安装完成了。
执行freeswitch -help就能看到相关命令提示
前提知识
- 安装完毕后,系统全局中就会注册有
freeswitch命令和fs_cli命令; freeswitch是启动freeswitch服务的开始命令。fs_cli是客户端命令,执行该命令就能连接到freeswitch服务器,就可以操作和查看freeswitch日志了。- 执行freeswitch之后,会自动连接上服务器并查看日志;
- 当你更改了相关配置后,按快捷键F6或者在命令行按Enter键并输入reloadxml命令进行更新配置操作,下面会列出所有快捷键
- 关闭freeswitch服务在命令行按Enter键并输入
fsctl shutdown命令,如果重启可以再键入freeswitch即可 - 重启freeswitch服务命令是
fsctl restart,但是我老是不成功; - freeswitch服务启动后,客户端SIP协议端口在5060,网页端SIP协议端口在5066(使用SIP协议状态码,底层是WS协议),外线是5080端口
修改配置
修改默认密码
使用默认密码注册用户拨打电话,系统会做拨号延迟。
我的配置文件在/etc/freeswitch,但是网上很多资料说在/usr/local/freeswitch/conf,根据自己的情况吧
cd /etc/freeswitch
vim vars.xml
将里面的默认密码1234修改一下,比如123456,如
<X-PRE-PROCESS cmd="set" data="default_password=123456"/>
然后输入:wq回车,保存退出
取消默认密码拨号延迟
如果你不想修改密码,可以将延迟密码得配置修改掉
找到文件/etc/freeswitch/dialplan/default.xml
<condition field="${default_password}" expression="^1234$" break="never">
<action application="log" data="CRIT WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING "/>
<action application="log" data="CRIT Open $${conf_dir}/vars.xml and change the default_password."/>
<action application="log" data="CRIT Once changed type 'reloadxml' at the console."/>
<action application="log" data="CRIT WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING "/>
<action application="sleep" data="10000"/>
</condition>
找到最后一行<action application="sleep" data="10000"/>,将10000改为3000,退出保存,F6刷新
配置IP地址
这一步主要是解决,拨号成功之不然可能打得通电话,但是没有声音。
cd sip_profiles
找到external.xml和internal.xml文件,将value字段修改为公网的IP地址,没公网写内网的
<param name="ext-rtp-ip" value="[external_ip]"/>
<param name="ext-sip-ip" value="[external_ip]"/>
改后
<param name="ext-rtp-ip" value="192.168.235.94"/>
<param name="ext-sip-ip" value="192.168.235.94"/>
至此,处于同一网络环境得两台分机互播就可以通话了,但是如果两台分机处于两个不通网段则没有声音
比如,freeswitch在公网,一台手机通过路由器连接freeswitch,一台手机通过4G链接服务器,它们之间可以互相拨打电话,但是接通之后没有声音。这是因为通过路由器连接freeswitch得分机,经过路由器后,IP地址做了转换。
所以最好改为
改后
<param name="ext-rtp-ip" value="autonat:192.168.235.94"/>
<param name="ext-sip-ip" value="autonat:192.168.235.94"/>
此外,还是这两个文件(external.xml和internal.xml),找到<param name="local-network-acl" value="localnet.auto"/>
修改为
<param name="local-network-acl" value="rfc1918.auto"/>
由于我是在公司内网服务器测试的,所以一定要让网络管理员开放硬件防火墙的端口号,不然也是不通的,这个问题就让我郁闷很久
需要注意的时,内网分机连接服务器是会做两部鉴权的,第一步时账号密码是否正确,第二部时检查客户端的ip地址是否在自己的acl池中,放开ip地址访问权限,需要修改
cd autoload_configs
vim acl.conf.xml
在该标签中添加允许连接的安全域名
<list name="domains" default="deny">
<node type="allow" cidr="0.0.0.0/0"/>
</list>
如果你不想放开所有的ip地址, 比如放开某一网段
<node type="allow" cidr="192.168.32.0/24"/>
放开某一固定的ip地址
<node type="allow" cidr="192.168.32.109/32"/>
里面的0,24和32是子网掩码转换为二进制后1的位数
至此,我们就扫清了所有的障碍,就可以通过客户端进行电话互联了
启动服务器
freeswitch #前台启动
freeswitch -nc #后台启动
freeswitch启动时会检测你的公网IP和内网IP并自动给你配置好。
系统默认会给你分配20个账号,分别是1000-1019;
客户端拨号
这个时候拿出客户端,比如Zoiper客户端配置
用户名:1000@118.190.209.12:5060
密码:123456
服务器:118.190.209.12:5060
其他都点skip
君语电话配置
SIP服务器: 118.190.209.12
用户名:1000
密码:123456
SIP端口号:5060
传输协议:TCP
网页端由于浏览器的安全限制会稍微麻烦一些,下面会专门讲解网页端的连接方法
当你都配置好了以后,发现还是链接不上,这是为什么呢,这是因为我们还少了两步,刚开始为了快速测试,可以先关闭系统的防火墙,再开启服务器的防火墙,比如去阿里云后台开放TCP、UDP的所有端口号
systemctl stop firewalld.service #停止firewall
systemctl disable firewalld.service #禁止firewall开机启动
如果生产环境,可以参考如下,将需要用的端口进行开放
| FireWall Ports | Network Protocol | Application Protocol | Description |
|---|---|---|---|
| 1719 | UDP | H.323 Gatekeeper RAS port | |
| 1720 | TCP | H.323 Call Signaling | |
| 3478 | UDP | STUN service | 用于 NAT 穿透 |
| 3479 | UDP | STUN service | 用于 NAT 穿透 |
| 5002 | TCP | MLP protocol server | |
| 5003 | UDP | Neighborhood service | |
| 5060 | UDP & TCP | SIP UAS | 用于 SIP 信令 (标准 SIP 端口,对于默认 ”Internal” 配置文件) |
| 5070 | UDP & TCP | SIP UAS | 用于 SIP 信令 (对于默认 “NAT” 配置文件) |
| 5080 | UDP & TCP | SIP UAS | 用于 SIP 信令 (对于默认 “External” 配置文件) |
| 8021 | TCP | ESL | 用于 mod_event_socket |
| 16384-32768 | UDP | RTP/ RTCP multimedia streaming | 用于 SIP、交换以及其他协议的语音或视频的数据传输 |
| 5066 | TCP | Websocket | 用于 WebRTC |
| 7443 | TCP | Websocket | 用于 WebRTC |
接下来在试一下,就可以联通了,freeswitch提供了测试号码
| 号码 | 说明 |
|---|---|
| 9664 | 保持音乐 |
| 9196 | echo,回音测试 |
| 9195 | echo,回音测试,延迟5秒 |
| 9197 | milliwatte extension,铃音生成 |
| 9198 | TGML 铃音生成示例 |
| 5000 | 示例IVR |
| 4000 | 听取语音信箱 |
| 33xx | 电话会议,48K(其中xx可为00-99,下同) |
| 32xx | 电话会议,32K |
| 31xx | 电话会议,16K |
| 30xx | 电话会议,8K |
| 2000-2002 | 呼叫组 |
| 1000-1019 | 默认分机号 |
自己测试的,freeswitch第一次启动,测试号码可以拨通,重启freeswitch后就拨不通了,但是不影响分机号码之间互播
网页端拨号
测试demo
先下载一个sip.js的demo sip.js + freeswitch 软电话(webRTC)demo
这个版本有点旧,但是亲测可以用
扫除浏览器安全限制
搭建https服务器
前端源文件下载完毕之后,接下来就可以启动一个服务器进行访问了,这个根据自己的使用习惯吧,只要能启动一个服务器即可。
我这列一下自己常用的方案,
一种是使用vscode插件live Server,另一种是使用了nodejs的一个插件browser-sync插件
这两种都可以生成https服务器,后者会更方便一些,不用考虑生成证书的事情,一行命令即可
browser-sync是一款浏览器同步工具,前端开发时编写html页面比较方便,现在有webpack等构建工具,这个基本不用这个了,这里我们只用他的生成服务器功能,启动时会关闭同步功能
先安装nodejs
然后安装插件
npm install -g browser-sync
在前端项目的根目录,打开cmd,并输入
browser-sync start --server --https --no-ghost-mode
意思时开启https服务器并关闭代码同步功能(幽灵模式)
这样我们就得到了一个https服务器了
设置白名单
接下来解决与服务器连接时,websocket的安全限制问题
在chrome地址栏输入
chrome://flags/#unsafely-treat-insecure-origin-as-secure
在里面填入,你要访问的freeswitch地址,比如
ws://192.168.235.94:5066
再把disabled改为enable
浏览器会有一个弹框,要求你重启浏览器,点击relaunch
手机同上,需要下载chrome浏览器,移动端不好测试,可以引入插件eruda.js方便调试查看日志
生成证书及配置
对于要发布上线的话,还得用域名和证书 官网教程
配置sip网关拨打外部电话
在这里我是在两个不通的服务器分别搭建了一套freeswitch服务,当然,freeswitch不仅能和freeswitch联通,还可以与其他电话服务器联通;
配置网关
首先在sip_profiles/external目录下创建一个xml文件,名字随便起,如gw_a.xml,同目录下有一个example.xml,复制一下里面的配置,然后在gw_a.xml文件中粘贴并修改如下
<gateway name="gw_a">
<!--/// account username *required* ///-->
<param name="username" value="1000"/>
<!--/// auth realm: *optional* same as gateway name, if blank ///-->
<param name="realm" value="118.190.209.12"/>
<!--/// username to use in from: *optional* same as username, if blank ///-->
<!--<param name="from-user" value="cluecon"/>-->
<!--/// domain to use in from: *optional* same as realm, if blank ///-->
<!--<param name="from-domain" value="asterlink.com"/>-->
<!--/// account password *required* ///-->
<param name="password" value="123456"/>
<!--/// extension for inbound calls: *optional* same as username, if blank ///-->
<!--<param name="extension" value="cluecon"/>-->
<!--/// proxy host: *optional* same as realm, if blank ///-->
<!--<param name="proxy" value="asterlink.com"/>-->
<!--/// send register to this proxy: *optional* same as proxy, if blank ///-->
<!--<param name="register-proxy" value="mysbc.com"/>-->
<!--/// expire in seconds: *optional* 3600, if blank ///-->
<!--<param name="expire-seconds" value="60"/>-->
<!--/// do not register ///-->
<param name="register" value="true"/>
<!-- which transport to use for register -->
<!--<param name="register-transport" value="udp"/>-->
<!--How many seconds before a retry when a failure or timeout occurs -->
<!--<param name="retry-seconds" value="30"/>-->
<!--Use the callerid of an inbound call in the from field on outbound calls via this gateway -->
<!--<param name="caller-id-in-from" value="false"/>-->
<!--extra sip params to send in the contact-->
<!--<param name="contact-params" value=""/>-->
<!-- Put the extension in the contact -->
<!--<param name="extension-in-contact" value="true"/>-->
<!--send an options ping every x seconds, failure will unregister and/or mark it down-->
<!--<param name="ping" value="25"/>-->
<!--<param name="cid-type" value="rpid"/>-->
<!--rfc5626 : Abilitazione rfc5626 ///-->
<!--<param name="rfc-5626" value="true"/>-->
<!--rfc5626 : extra sip params to send in the contact-->
<!--<param name="reg-id" value="1"/>-->
</gateway>
这里面的username是第二台freeswitch中已经注册的用户名,password就是该用户的登录密码,realm是其服务器地址,可以是IP或者IP:Port,如果还需要其方面的要求,可以自行放开注释部分进行配置;
保存好以后,重启服务,freeswitch就会以1000用户的身份连接上第二台freeswitch服务器当中了
修改拨号规则
但是我们第一台freeswitch服务器中的用户如何给第二台freeswitch中的用户拨打电话呢?
这时候我们就需要修改拨号规则,通过在拨号中添加前缀,根据前缀将该拨号转发给我们刚刚建立的网关,然后让网关代理我们去拨打其他电话服务中的用户.举个例子,我们要给1001打电话,我们在1001前面加上0或9(数字随意),就是91001,拨打时发现以9开头,然后去掉9,把1001转发到gw_1.xml网关,网关拨打1001,接通后建立本机号码与外机号的连接与消息转发。
下面我们来配置这个拨号规则
打开/dialplan/public目录,(《freeswitch权威指南》说建在default目录下,但是我没有成功,但是在public目录下是可以的)创建call_out.xml,内容如下
<include>
<extension name="callout">
<condition field="destination_number" expression="^9(\d+)$">
<action application="bridge" data="sofia/gateway/gw_a/$1"/>
</condition>
</extension>
</include>
其中^9(\d+)$是正则表达式,就是匹配到9以后,把9后面的数字\d+用$1代替传递到下面网关中,然后用bridge程序去拨打号码
重启服务器生效
拨打电话流程
先理解SIP Profile的几个概念
-
一个Profile就是一个UA;一个UA就是一个“IP地址: 端口”;
-
fs的/sip_profiles目录下主要有三个东西(不考虑ipv6):external.xml、internal.xml、 /external目录。其中external.xml和internal.xml就是两个Profile;而/external里的xml文件是我们自定义的外部网关,这些外部网关都会被fs装入external.xml中;
-
internal.xml默认运行在5060端口,external.xml默认运行在5080端口。注意不要因为它们的名称而把它们理解为内部和外部。 60端口和80端口的区别就是前者会对sip消息鉴权而后者不需要(各自xml中的auth-calls标签定义)。
本地用户互拨流程
本地user/1000拨打本地user/1001:
- 因为user/1000注册在5060端口,所以向fs的5060端口发送INVITE请求;
- INVITE请求到达internal这个Profile所配置的UA(internal.xml);
- 此UA会对此INVITE请求进行鉴权(因为auth-calls=ture);
- 先检查ACL(acl.conf.xml),然后进行Digest鉴权(directory/default/1000.xml中的用户名和密码);
- 若鉴权通过则开始寻找路由,在1000.xml中的user_context标签即是路由;
- 这里以user_context的值是default为例,则进入diaplan/default.xml中寻找路由;
- diaplan/default.xml会找到1001这个用户,并执行bridge user/1001;
- bridge user/1001这个呼叫字符串会再次查找directory用户目录并找到directory/default/1001.xml;
- 因为1001是被叫,所以fs会进一步找到它实际注册位置,内部用户的实际注册位置在conf/directory/default.xml中dial-string标签配置。在这个标签中可以看到调用了sofia_contact这个API,这个API会查找数据库并找到user/1001的contact地址并返回真正的呼叫字符串。
本地用户拨打外部号码流程
本地user/1000拨打外部号码40012345:
- 因为user/1000注册在5060端口,所以向fs的5060端口发送INVITE请求;
- INVITE请求到达internal这个Profile所配置的UA(internal.xml);
- 此UA会对此INVITE请求进行鉴权(因为auth-calls=ture);
- 先检查ACL(acl.conf.xml),然后进行Digest鉴权(directory/default/1000.xml中的用户名和密码);
- 若鉴权通过后会找到该用户的配置文件(即1000.xml),在1000.xml中的user_context标签中配置了路由,所以fs会根据此配置进行路由查找:以默认配置为例:,此时进入diaplan/default.xml中寻找路由;
- 对于外部号码,default.xml中一般会将请求送到外部网关,例如:bridge sofia/gateway/gw1/40012345这样;
- 其中gw1是我们配置的一个网关。本文开头解释过,网关最终都会被装入external.xml,而external这个Profile运行在5080端口。因此,该INVITE请求最终会通过本机的5080端口发往gw1网关(在gw1对应的xml中配好了目的地的ip和端口)。
本地用户接听外部来电流程
外部送来的sip消息可能送到5080端口,也可能送到5060端口。
- 如果送到5080端口:
- 外部的INVITE请求到达fs的5080端口,即external这个Profile,external不会对来话进行鉴权,直接进行路由;
- 在external.xml的context标签中配置了路由,以默认配置为例:,那么就会去diaplan/public.xml中查找路由,路由中设定了接下来的操作,是转到一个本地用户,还是继续bridge到其他地方;
- 如果是路由将请求转向一个本地用户(例如user/1000),那么将会找到这个用户的配置文件1000.xml,并根据1000.xml中user_context标签的配置继续进行路由(参考上面内部用户互拨流程);
- 如果送到5060端口:
- 外部的INVITE请求到达fs的5060端口,即internal这个Profile,internal会对来话进行鉴权;
- 先ACL鉴权,再Digest鉴权。如果INVITE请求发送者的ip在autoload_configs/acl.conf.xml中配置过,则ACL鉴权通过,直接根据external.xml中context标签的配置进行路由;如果未通过ACL鉴权则进行Digest鉴权,如果通过Digest鉴权的认证就会找到一个内部用户(例如user/1000),进而根据其用户目录(即1000.xml)中user_context标签的配置进行路由。
Event Socket
freeswitch提供了一种不登录后台就可以对freeswitch进行管理的方式
开启event_socket,开放8021端口
配置autoload_configs/acl.conf.xml文件:
<list name="domains" default="deny">
<!-- domain= is special it scans the domain from the directory to build the ACL -->
<node type="allow" domain="$${domain}"/>
<!-- use cidr= if you wish to allow ip ranges to this domains acl. -->
<!-- <node type="allow" cidr="192.168.0.0/24"/> -->
<!-- <node type="allow" cidr="192.168.168.0/24"/> -->
<node type="allow" cidr="0.0.0.0/0"/>
</list>
配置autoload_configs/event_socket.conf.xml文件:
<configuration name="event_socket.conf" description="Socket Client">
<settings>
<param name="nat-map" value="false"/>
<param name="listen-ip" value="0.0.0.0"/>
<param name="listen-port" value="8021"/>
<param name="password" value="ClueCon"/>
<param name="apply-inbound-acl" value="domains"/>
<!--<param name="apply-inbound-acl" value="loopback.auto"/>-->
<!--<param name="stop-on-bind-error" value="true"/>-->
</settings>
</configuration>
执行命令
reload mod_event_socket //更新配置
查看端口运行情况
netstat -apn | grep 8021
尝试telnet连接
telnet 192.168.235.96 8021
输入 auto ClueCue 然后按两下回车
输入
api version
api status
api sofia status
api uptime
快捷键
| 快捷键 | 命令 |
|---|---|
| F1 | help |
| F2 | status |
| F3 | show channels |
| F4 | show calls |
| F5 | sofia status |
| F6 | reloadxml |
| F7 | console loglevel 7 |
| F8 | console loglevel 0 |
| F9 | sofia status profile internal |
| F10 | sofia profile internal siptrace on |
| F11 | sofia profile internal siptrace off |
| F12 | version |
常用命令
-
freeswitch启动 -
freeswitch -nc启动并后台运行 -
freeswitch -stop停止 -
fs_cli -H 127.0.0.1 -P 8021 -p password进入客户端 -
/exit/bye/quitCtrl+D退出客户端 -
sofia status profile local查询网关状态 -
sofia profile local restart重启网关 -
sofia status profile internal reg查询注册用户数 -
sofia status profile internal查看当前注册的用户数量 -
sofia profile external register gw重新注册网关 -
sofia global siptrace on开启sip消息显示 -
sofia global siptrace off关闭sip消息显示 -
originate user/1003 &echo使用fs_cli进行呼叫,其中&echo会把听到的声音返回给发出者 -
bgapi originate {absolute_codec_string=PCMU}user/1009 018162330576 xml default音频编码部分设备不支持可以使用这个 -
reloadxml重新加载xml文件 -
find / -name external -type d查找文件夹 -
echo a b c | xargs -n 1 cp -v ./gwl.xml复制文件到多个文件夹 -
ps aux | grep freeswitch查看进程是否存在 -
netstat -anp | grep freeswitch查看freeswitch的端口情况 -
netstat -apn | grep 8021查看具体某个端口 -
rpm -ql freeswitch查看某应用的安装路径信息
freeswitch 命令
# freeswitch -help
Usage: freeswitch [OPTIONS]
These are the optional arguments you can pass to freeswitch:
-nf -- no forking
-reincarnate -- restart the switch on an uncontrolled exit
-reincarnate-reexec -- run execv on a restart (helpful for upgrades)
-u [user] -- specify user to switch to
-g [group] -- specify group to switch to
-core -- dump cores
-help -- this message
-version -- print the version and exit
-rp -- enable high(realtime) priority settings
-lp -- enable low priority settings
-np -- enable normal priority settings
-vg -- run under valgrind
-nosql -- disable internal sql scoreboard
-heavy-timer -- Heavy Timer, possibly more accurate but at a cost
-nonat -- disable auto nat detection
-nonatmap -- disable auto nat port mapping
-nocal -- disable clock calibration
-nort -- disable clock clock_realtime
-stop -- stop freeswitch
-nc -- do not output to a console and background
-ncwait -- do not output to a console and background but wait until the system is ready before exiting (implies -nc)
-c -- output to a console and stay in the foreground
Options to control locations of files:
-base [basedir] -- alternate prefix directory
-cfgname [filename] -- alternate filename for FreeSWITCH main configuration file
-conf [confdir] -- alternate directory for FreeSWITCH configuration files
-log [logdir] -- alternate directory for logfiles
-run [rundir] -- alternate directory for runtime files
-db [dbdir] -- alternate directory for the internal database
-mod [moddir] -- alternate directory for modules
-htdocs [htdocsdir] -- alternate directory for htdocs
-scripts [scriptsdir] -- alternate directory for scripts
-temp [directory] -- alternate directory for temporary files
-grammar [directory] -- alternate directory for grammar files
-certs [directory] -- alternate directory for certificates
-recordings [directory] -- alternate directory for recordings
-storage [directory] -- alternate directory for voicemail storage
-cache [directory] -- alternate directory for cache files
-sounds [directory] -- alternate directory for sound files
sofia
> sofia
USAGE:
--------------------------------------------------------------------------------
sofia global siptrace <on|off>
sofia capture <on|off>
watchdog <on|off>
sofia profile <name> [start | stop | restart | rescan] [wait]
flush_inbound_reg [<call_id> | <[user]@domain>] [reboot]
check_sync [<call_id> | <[user]@domain>]
[register | unregister] [<gateway name> | all]
killgw <gateway name>
[stun-auto-disable | stun-enabled] [true | false]]
siptrace <on|off>
capture <on|off>
watchdog <on|off>
sofia <status|xmlstatus> profile <name> [reg [<contact str>]] | [pres <pres str>] | [user <user@domain>]
sofia <status|xmlstatus> gateway <name>
sofia loglevel <all|default|tport|iptsec|nea|nta|nth_client|nth_server|nua|soa|sresolv|stun> [0-9]
sofia tracelevel <console|alert|crit|err|warning|notice|info|debug>
sofia help
--------------------------------------------------------------------------------
参考资料
- 《freeSWITCH权威指南》
- FreeSWITCH 电话软交换系统搭建(一):初始化安装
- Configure FreeSWITCH
- Freeswitch配置SIP网关拨打外部电话