FreeSWITCH 能注册、能呼入、能呼出,那么基本的就算配置完成了。此时直接把 5060、5080 和 8021 开到公网,离“可以上线”还差一些安全配置。
盗打通常不是某个配置配错了。攻击者扫到 SIP 端口,猜中分机口令,或者让未经认证的 INVITE 进入了带 bridge 的出站规则,电话就可能从你的运营商账户出局。Europol (欧洲刑警组织)对国际收益分成欺诈的描述很典型:呼叫量突然增大,通话时间偏长,目的地集中在少数高资费号码。账单只是结果,前面的每一层都应该有机会把它挡住。
这篇教程基于 Ubuntu 和 FreeSWITCH vanilla 配置,也接受软电话、移动网络和家庭宽带地址会变化这个现实。做完后,你应该能看到五个结果:合法分机能从动态公网地址注册;重复错误认证会触发临时封禁;只有运营商公布的地址能访问中继入口;普通分机只能拨明确放行的号码;未允许的国际或高资费号码在本机被拒绝,不会送到运营商。
先确认自己正在改哪套配置
软件包安装常见的配置根目录是 /etc/freeswitch,源码默认安装常见的是 /usr/local/freeswitch/conf。FreeSWITCH 官方入门文档也区分了这两种路径。先查运行中的实际值,别在错误目录里改半天:
# 读取运行中实例实际使用的配置目录和日志目录
fs_cli -x 'global_getvar conf_dir'
fs_cli -x 'global_getvar log_dir'
# 查看 SIP profile 和系统当前监听的常见端口
fs_cli -x 'sofia status'
sudo ss -lntup | rg ':(5060|5080|8021|5066|7443)\b'
如果系统没有 rg,可以改用 grep -E。把查到的配置目录记为 FS_CONF,下文示例按 /etc/freeswitch 写。改配置前复制一份备份,并确认磁盘空间足够。Profile 重启会中断该 Profile 上的现有通话,要放进维护窗口。
示例里的 203.0.113.0/24 是运营商信令网段占位符,属于文档保留地址,不能原样用于生产。分机侧按动态公网地址直注册设计,不设置来源 IP 白名单。
固定来源做白名单,动态分机只开放必要端口
把 SIP 端口从 5060 改成别的数字,只能少一点日志噪声,不能形成可靠防护。端口扫描照样能找到它,只是时间问题。运营商中继地址通常固定,可以按来源做白名单;公网分机地址经常变化,只能开放真正使用的传输协议,再让 FreeSWITCH 认证和自动封禁接手。
下面用 Ubuntu 自带的 UFW 演示。远程操作时,先允许你实际使用的 SSH 入口,再启用防火墙。否则可能把自己锁在服务器外面。Ubuntu 官方文档支持按来源网段和目标端口编写规则。
# 先保留 SSH 管理入口;如果 SSH 不是默认配置,请按真实端口和来源 IP 固定
sudo ufw allow OpenSSH
# 默认拒绝新建入站连接,保留正常出站连接
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 只允许运营商信令网段访问 external profile;按真实传输协议删减 UDP 或 TCP
sudo ufw allow proto udp from 203.0.113.0/24 to any port 5080
sudo ufw allow proto tcp from 203.0.113.0/24 to any port 5080
# 公网分机使用动态地址时,开放实际使用的 internal SIP 传输
# 本例只演示 UDP;话机不用 TCP 时不要顺手开放 5060/tcp
sudo ufw allow 5060/udp
# 公网分机还需要媒体可达;端口范围必须与 vars.xml 保持一致
# 若已按 Profile 拆分媒体地址或边界防火墙,请按实际拓扑进一步收紧
sudo ufw allow 16384:32768/udp
# 检查规则后再启用,并保留防火墙日志
sudo ufw status verbose
sudo ufw logging on
sudo ufw enable
运营商的 SIP 信令地址和 RTP 媒体地址可能不是一批 IP。白名单写错时,常见现象是来话无声、单通或注册反复掉线。运营商入口不要为了省事改成 0.0.0.0/0,应该索取完整的信令网段、媒体网段和传输协议。若所有话机都支持 SIP over TLS,可改为只开放 TLS 监听,但这还需要正确的证书、终端校验和配套 Profile 配置,不能只把端口换成 5061。
云服务器还要同步检查安全组。主机 UFW 放行而云安全组拒绝,业务不通;云安全组对全网开放而主机规则被误清空。
FreeSWITCH ACL 再拦一次
主机防火墙挡在 FreeSWITCH 外面,ACL 在应用内部复核来源。两层使用同一份经过确认的地址清单,能减少某一层误改后的影响。
在 autoload_configs/acl.conf.xml 的 <network-lists> 内增加运营商列表:
<!-- 运营商中继白名单:默认拒绝,只允许官方公布的信令网段 -->
<list name="trusted-carriers" default="deny">
<node type="allow" cidr="203.0.113.0/24"/>
</list>
在 sip_profiles/external.xml 的 <settings> 中应用运营商白名单,同时保留 public context:
<!-- 外部中继来话只能进入 public context,不能直达带出站能力的 default context -->
<param name="context" value="public"/>
<!-- external profile 常按运营商 IP 建立信任,未通过 ACL 的请求直接拒绝 -->
<param name="apply-inbound-acl" value="trusted-carriers"/>
<param name="auth-calls" value="false"/>
auth-calls=false 不是让全世界免密调用你的网关。它适合运营商中继,前提是来源 IP 已经被防火墙和 ACL 限住,后面的 public dialplan 也只接收你拥有的号码。
动态公网分机不能套用 apply-register-acl,否则地址一变就会注册失败。应在 sip_profiles/internal.xml 的 <settings> 中显式收紧认证:
<!-- internal profile 上的 INVITE 必须完成 SIP 摘要认证 -->
<param name="auth-calls" value="true"/>
<!-- 盲注册和盲认证只适合测试环境,生产配置显式关闭 -->
<param name="accept-blind-reg" value="false"/>
<param name="accept-blind-auth" value="false"/>
<!-- 注册认证用户名必须与 From 用户一致,减少身份混用 -->
<param name="inbound-reg-force-matching-username" value="true"/>
<!-- 业务不需要一号多终端时关闭多注册;共享分机不要照抄这一项 -->
<param name="multiple-registrations" value="false"/>
官方 Sofia Profile 文档说明,accept-blind-reg 和 accept-blind-auth 只适合测试,inbound-reg-force-matching-username 用于拒绝认证用户名与 From 用户不一致的注册。multiple-registrations=false 会影响同一分机的多终端场景,确认业务不需要后再显式设置。
官方 ACL 文档说明,reloadacl 会重新加载 XML 并重建网络列表。apply-inbound-acl 和 internal Profile 参数变更需要安排维护窗口重启对应 Profile:
# 先验证 XML,再重载 ACL;命令返回错误时不要继续重启 Profile
fs_cli -x 'reloadxml'
fs_cli -x 'reloadacl'
# Profile 重启会影响现有通话,只在维护窗口执行
fs_cli -x 'sofia profile external restart'
fs_cli -x 'sofia profile internal restart'
把重复注册失败交给 Fail2ban
公网直注册意味着 5060 会持续收到扫描和口令猜测。FreeSWITCH 的 mod_fail2ban 会监听 Sofia 事件,把明确的注册失败写进独立日志,再由系统 Fail2ban 封禁来源。它不是所有软件包都默认加载,先确认模块和日志目录:
# 确认模块是否存在;返回 false 时应安装或构建与当前版本匹配的模块
fs_cli -x 'module_exists mod_fail2ban'
# 模块存在但未加载时加载它,并确认专用日志所在的根目录
fs_cli -x 'load mod_fail2ban'
fs_cli -x 'global_getvar log_dir'
为了让重启后仍自动加载,在 autoload_configs/modules.conf.xml 中加入下面一行。模块配置 autoload_configs/fail2ban.conf.xml 的默认日志通常是 $${log_dir}/fail2ban.log,但仍要在本机核对。
<!-- 记录 Sofia 注册失败事件,供系统 Fail2ban 读取 -->
<load module="mod_fail2ban"/>
用 Ubuntu 软件包安装系统 Fail2ban:
# 从系统仓库安装,避免与发行版的 systemd 和 UFW 配置脱节
sudo apt update
sudo apt install fail2ban
然后新建 /etc/fail2ban/filter.d/freeswitch-register.local:
[Definition]
# 中文注释:锚定 mod_fail2ban 的完整事件行,避免普通日志文本误封地址
failregex = ^A registration failed user\[[^]]*\] ip\[<HOST>\] at\[.*\]$
# 中文注释:当前没有额外排除式;管理地址应通过 jail 的 ignoreip 单独配置
ignoreregex =
再新建 /etc/fail2ban/jail.d/freeswitch-register.local。下列阈值只是起点,呼叫中心、NAT 出口共享地址和批量终端上线时必须按正常失败基线调整:
[freeswitch-register]
# 中文注释:启用 FreeSWITCH 注册失败监控,并引用上面的过滤器
enabled = true
filter = freeswitch-register
# 中文注释:替换成 global_getvar log_dir 返回目录下的真实文件
logpath = /var/log/freeswitch/fail2ban.log
# 中文注释:官方 ufw 动作要求 UFW 已启用;封禁后应检查实际生成的规则
banaction = ufw
# 中文注释:十分钟八次失败后封一小时,必须结合正常业务调整
maxretry = 8
findtime = 10m
bantime = 1h
应用前先测试正则,避免一个宽泛表达式把正常请求误封。确认有匹配、无误匹配后再重启服务:
# 用真实日志验证过滤器;Matched 与 Missed lines 都要抽样检查
sudo fail2ban-regex /var/log/freeswitch/fail2ban.log /etc/fail2ban/filter.d/freeswitch-register.local
# 重启后确认 jail 已启动,并观察当前失败与封禁计数
sudo systemctl restart fail2ban
sudo fail2ban-client status freeswitch-register
FreeSWITCH 官方模块文档给出了注册失败日志格式;Fail2ban 官方 UFW 动作要求 UFW 已启用。不要从承载 SSH 管理的同一个公网 IP 做错误口令压测。测试地址被封后,可用 sudo fail2ban-client set freeswitch-register unbanip 测试IP 解封。
Fail2ban 只能压低同一地址的重复请求。攻击者轮换 IP 时效果会下降;凭据已经泄露并成功认证时,它也不会触发。后面的出站权限、运营商限额和监控仍是必须项。
删掉演示账号,也别让所有分机共用一个密码
Vanilla 配置默认带有 1000~1019 共 20 个测试用户,并通过 default_password 共用 1234。这不是猜测,官方入门文档直接列出了这些默认值。未使用的测试用户应移出 directory/default/ 的加载范围。还在使用的用户要改成独立账号,并给每个分机单独生成随机口令。
# 生成一个 48 位十六进制随机口令;每个分机重新生成一次
openssl rand -hex 24
用户目录可以这样配置:
<include>
<!-- 2001 是示例分机号,password 必须替换成刚生成的独立随机值 -->
<user id="2001">
<params>
<param name="password" value="REPLACE_WITH_RANDOM_SECRET"/>
</params>
<variables>
<!-- 认证成功后进入内部拨号上下文 -->
<variable name="user_context" value="default"/>
<!-- 只授予国内移动号码权限;标签名称由运维自行定义 -->
<variable name="toll_allow" value="domestic-mobile"/>
<variable name="accountcode" value="2001"/>
</variables>
</user>
</include>
分机号不是秘密,改成生僻号码也不能代替强口令。口令不要复用运营商后台、SSH 或其他分机的密码,也不要写进工单截图和日志。离职、设备丢失或软电话配置泄露时,只撤销对应分机,不必让所有话机一起换密码。
外部来话只能经过 public context
FreeSWITCH 官方 public context 文档把这条隔离线写得很清楚:external profile 收到的未认证来话进入 public,认证后的内部用户才使用带完整功能的 default。把 external 的 context 改成 default,等于让外部请求接触内部出站规则。
检查 dialplan/public/ 时,只保留真实 DID 的明确匹配。下面把一个示例号码转给内部 2001 分机:
<include>
<extension name="inbound-main-number">
<!-- 只匹配运营商分配给你的完整 DID,不使用任意数字通配 -->
<condition field="destination_number" expression="^861234501234$">
<!-- 命中后只转到明确的内部目标 -->
<action application="transfer" data="2001 XML default"/>
</condition>
</extension>
</include>
不要在 public 里写 ^\d+$ 后直接 bridge 到运营商,也不要把任意外部号码 transfer 到 default 再继续匹配。来话显示号码可以伪造,不能拿 Caller ID 当认证凭据。
出站规则从默认拒绝开始
分机通过认证,不代表它应该拥有所有呼叫权限。toll_allow 能给用户打标签,但只有 dialplan 实际检查这个变量时才生效。官方拨号条件文档也明确把目录变量和 dialplan 条件分成了两步。
下面的例子只允许带 domestic-mobile 权限的用户拨打手机号。它接受 11 位号码或带 86 的号码,送给运营商前统一补成 86 格式:
<include>
<extension name="allow-mainland-mobile">
<!-- 先核对认证用户是否拥有这一类出站权限 -->
<condition field="${toll_allow}" expression="(^|,)domestic-mobile(,|$)">
<!-- 只接收明确号码格式,拒绝空号、短号和任意国际前缀 -->
<condition field="destination_number" expression="^(?:86)?(1[3-9]\d{9})$">
<!-- carrier 必须替换成实际 gateway 名称 -->
<action application="bridge" data="sofia/gateway/carrier/86$1"/>
<anti-action application="hangup" data="CALL_REJECTED"/>
</condition>
<anti-action application="hangup" data="CALL_REJECTED"/>
</condition>
</extension>
</include>
这段规则要替换原有的宽泛出站规则,不是和 ^\d+$、^(.+)$ 一起留着。旧规则如果先匹配并完成 bridge,新限制根本没有机会执行。固定电话、紧急号码、客服短号等业务确实需要时,再为它们增加单独的明确规则和权限标签。
国际电话和高资费号码默认不写路由。确实有人需要时,为少数账号增加独立权限,并按国家或号码段放行。不要给所有用户一个 international 标签,也不要用 00、+ 后面任意数字的规则一次全开。
把 Event Socket 留在本机
Event Socket 默认常用 8021 端口。客户端通过认证后可以执行 FreeSWITCH API 命令,它的风险远高于一个普通查询端口。官方 Event Socket 文档建议绑定明确地址、设置窄范围 ACL 和强密码。
只供本机 fs_cli 或本机程序使用时,在 autoload_configs/event_socket.conf.xml 中写清楚:
<configuration name="event_socket.conf" description="Socket Client">
<settings>
<!-- 只监听本机,公网和局域网主机都不能直接连接 -->
<param name="listen-ip" value="127.0.0.1"/>
<param name="listen-port" value="8021"/>
<!-- 使用独立强口令,不保留示例值 ClueCon -->
<param name="password" value="REPLACE_WITH_ANOTHER_RANDOM_SECRET"/>
<param name="apply-inbound-acl" value="loopback.auto"/>
</settings>
</configuration>
远程管理优先通过 SSH 隧道或专用管理网进入,不要给 UFW 增加一条对全网开放 8021 的规则。业务系统必须远程连接时,绑定专用内网地址,再用独立管理网段 ACL 限制来源。
用 CDR 和运营商限制缩短止损时间
防护配置可能被人改错,分机终端也可能泄露。运营商侧再加一层限制很有价值:关闭不使用的国际和高资费目的地,限制最大并发,设置消费上限与异常通知。每家运营商提供的功能和名称不同,阈值也应该按你的正常话量来定,不适合照抄一个固定数字。
FreeSWITCH vanilla 默认加载 mod_cdr_csv。根据官方 CDR 文档,主记录通常位于 FreeSWITCH 日志目录下的 cdr-csv/Master.csv。先确认模块和实际目录:
# 确认 CDR 模块已加载,并找到当前日志根目录
fs_cli -x 'module_exists mod_cdr_csv'
fs_cli -x 'global_getvar log_dir'
# 观察当前通话与注册状态,基线应来自你的正常业务时段
fs_cli -x 'show calls count'
fs_cli -x 'sofia status profile internal reg'
监控至少要能发现这些变化:短时间内呼叫量突然上升;同一分机集中拨向陌生国家或号码段;非工作使用时段出现持续长话;运营商余额或账单增长偏离正常水平。CDR 用来发现和追溯,不能替代前面的阻断规则。
按五个结果做验收
不要只看 reloadxml 返回 +OK。安全配置要从不同网络位置真的试一遍,而且不要拨真实高资费号码。
- 从一个动态公网地址使用正确凭据注册测试分机,预期注册成功并能在
sofia status profile internal reg中看到。 - 从不承载 SSH 管理的测试地址反复提交错误口令,达到阈值后预期出现在
fail2ban-client status freeswitch-register的封禁列表中;测试结束后及时解封。 - 从非运营商地址向 5080 发送 SIP 请求,预期被防火墙或 ACL 拒绝;合法运营商来话仍能命中真实 DID。
- 使用带
domestic-mobile权限的测试分机拨一个你控制的大陆手机号,预期正常接通并产生 CDR。 - 拨一个不在允许格式中的测试号码,例如
00870123456789,预期 FreeSWITCH 本机返回拒绝,运营商后台没有对应呼叫尝试。
验收失败时,沿着“主机防火墙 → FreeSWITCH ACL/Fail2ban → SIP 认证 → context → toll_allow → 号码规则 → gateway”这个顺序查。它和一次呼叫实际经过的路径一致,定位会快很多。
容易踩坑的点
Fail2ban 要等失败发生后才封 IP,还会遇到攻击者换地址的问题。它适合保护无法按来源白名单收口的公网分机入口;地址固定的运营商入口仍应在防火墙和 ACL 直接拒绝无关来源。
TLS 能保护 SIP 信令在传输中的内容,却不会自动修复宽泛的出站规则,也不会替你关闭国际目的地。它值得部署,但不要把“启用了 TLS”理解成“不会盗打”。
WebSocket 5066、WSS 7443、IPv6 SIP Profile 和管理面板经常被漏查。没有使用就关闭或不放行;正在使用就给它们同样的来源限制、认证和日志策略。IPv4 防火墙规则不会自动保护 IPv6。
上线前按这个清单检查一下
- 未使用的 vanilla 测试账号已经移除,所有分机使用独立随机口令。
- 5060 只开放话机实际使用的传输,强认证和注册失败封禁已经生效;5080 只对运营商信令地址开放。
- RTP 网段按运营商资料配置,通话双向语音已经验证。
- external profile 保持
publiccontext,public中没有任意号码出站或无条件转入default的规则。 - 每个分机按需求授予
toll_allow,出站 dialplan 同时检查权限和明确号码格式。 - 8021 绑定本机或专用管理网,示例口令已经替换。
- CDR 正常落盘,运营商侧的目的地、并发、消费上限和通知已经设置。
- 已从动态公网完成正确注册、错误认证封禁、运营商呼入、允许呼出和拒绝呼出的验收。
如果你需要排查现有 FreeSWITCH 是否还留着盗打路径,可以准备 FreeSWITCH 版本、安装方式、已脱敏的 SIP Profile、ACL、directory、public/default dialplan、监听端口,以及运营商提供的信令和媒体网段。请不要发送分机密码、运营商密钥、真实账单或内部 IP。相关代码和联系方式可以从我的 GitHub 与个人博客找到。