FreeSWITCH 对接 SIP 中继踩坑记录

最近在项目中用 FreeSWITCH 对接运营商 SIP 中继,过程中踩了不少坑。把这些经验整理出来,希望能帮后来人少走弯路。


背景

项目需求是搭建一套企业呼叫中心系统,FreeSWITCH 作为核心媒体服务器,通过 SIP 中继连接运营商线路,实现外呼和接听 PSTN 电话。


坑一:注册模式 vs 点对点模式,一开始就选错了

刚拿到运营商给的对接文档,上面写着 SIP 服务器地址、用户名、密码,我想当然地用注册模式(register)去对接。配好 gateway 之后发现:注册状态一直是 TRYING,根本注册不上。

反复排查后才搞清楚------这家运营商用的是IP 鉴权(点对点模式),不需要注册。他们给的"用户名密码"其实是管理后台的登录凭据,不是 SIP 认证信息。

正确做法: 先跟运营商确认鉴权方式。如果是 IP 鉴权,gateway 配置里不需要填 register 相关参数,只需要配置 proxy 地址,并确保 FreeSWITCH 的出口 IP 已在运营商侧加白。

xml 复制代码
<!-- IP 鉴权模式的 gateway 配置示例 -->
<gateway name="my_trunk">
  <param name="realm" value="sip.carrier.com"/>
  <param name="proxy" value="sip.carrier.com"/>
  <param name="register" value="false"/>
  <param name="caller-id-in-from" value="true"/>
</gateway>

坑二:NAT 导致单通、无声

这是 VoIP 领域最经典的坑,但每次遇到还是让人头疼。

症状是:呼叫能建立,但只有一端能听到声音,或者双方都无声。用 tcpdump 抓包一看,RTP 包的目标地址是内网 IP(比如 192.168.x.x),对端根本收不到。

排查思路:

FreeSWITCH 在 NAT 环境下,SDP 里携带的媒体地址默认是本机地址。如果服务器在内网且通过 NAT 出去,对端看到的是一个不可达的私有地址。

解决方案:vars.xml 中配置外部 IP 和本地网段:

xml 复制代码
<X-PRE-PROCESS cmd="set" data="external_rtp_ip=你的公网IP"/>
<X-PRE-PROCESS cmd="set" data="external_sip_ip=你的公网IP"/>
<X-PRE-PROCESS cmd="set" data="local_ip_v4=内网IP"/>

同时在 SIP Profile(通常是 external.xml)中确认:

xml 复制代码
<param name="ext-rtp-ip" value="$${external_rtp_ip}"/>
<param name="ext-sip-ip" value="$${external_sip_ip}"/>

另外别忘了防火墙要放开 RTP 端口范围(默认 16384-32768/UDP),很多人只开了 5060 就觉得完事了。


坑三:主叫号码被运营商拒绝

外呼时 FreeSWITCH 日志显示 403 Forbidden603 Decline

原因是发出去的 SIP INVITE 里,From 头域的号码不在运营商允许的主叫号码池里。有些运营商对主叫号码管控很严格,你不能随便填。

解决方案: 在 dialplan 或 gateway 配置中显式设置合法的主叫号码:

xml 复制代码
<!-- dialplan 中设置 -->
<action application="set" data="effective_caller_id_number=02812345678"/>
<action application="set" data="effective_caller_id_name=02812345678"/>

还有个细节:有些运营商要求主叫号码带区号但不带"0"前缀,有些又必须带。这种格式问题没有统一标准,只能跟运营商逐一确认。


坑四:编解码协商失败

呼叫建立后秒挂,日志里出现 488 Not Acceptable Here

抓包看 SDP,发现 FreeSWITCH 提供了一堆编解码(PCMU、PCMA、G729、Opus...),但运营商只支持 PCMA(G.711A),而且对 ptime 也有要求,必须是 20ms。

解决方案: 在 gateway 或 dialplan 里限定编解码:

xml 复制代码
<!-- 在 dialplan 里 bridge 之前设置 -->
<action application="export" data="absolute_codec_string=PCMA@20i"/>

或者在 gateway 配置中:

xml 复制代码
<param name="codec-prefs" value="PCMA"/>

教训是:不要假设运营商支持多种编解码,对接前一定要确认支持的编解码列表和 ptime 要求。


坑五:DTMF 不生效

IVR 场景下,用户按键没有反应。FreeSWITCH 这边完全收不到 DTMF 信号。

DTMF 的传输方式有三种:RFC 2833(RTP 内带外)、SIP INFO、In-band(带内音频)。FreeSWITCH 默认用 RFC 2833,但运营商侧可能用的是 SIP INFO,两边不一致就收不到。

解决方案: 在 SIP Profile 中调整 DTMF 模式:

xml 复制代码
<!-- 匹配运营商的 DTMF 方式 -->
<param name="dtmf-type" value="rfc2833"/>
<!-- 或者 -->
<param name="dtmf-type" value="info"/>

如果实在搞不定,可以尝试 start_dtmf application 来做带内检测,作为兜底方案:

xml 复制代码
<action application="start_dtmf"/>

坑六:并发呼叫一多就出问题

少量呼叫测试一切正常,一上并发就出现各种奇怪现象:呼叫失败率飙升、音频断断续续、FreeSWITCH 进程 CPU 占满。

排查下来有几个原因:

  1. 文件描述符限制 :Linux 默认的 ulimit 太低,FreeSWITCH 处理大量并发连接时会耗尽。需要在 /etc/security/limits.conf 中调大,同时修改 FreeSWITCH 的启动脚本。

  2. 数据库性能:FreeSWITCH 默认用 SQLite 存储通道信息,并发高了之后 SQLite 成为瓶颈。切换到 PostgreSQL 或关闭不必要的 CDR 写入可以显著改善。

  3. RTP 端口耗尽:默认端口范围 16384-32768,大约 8000 个端口,每路通话需要至少 2 个端口(收发各一个)。如果并发超过 4000 路就可能不够用,需要扩大范围。

xml 复制代码
<!-- switch.conf.xml 中调整 RTP 端口范围 -->
<param name="rtp-start-port" value="10000"/>
<param name="rtp-end-port" value="60000"/>

坑七:时间戳和计费对不上

运营商出的账单和我们系统记录的通话时长对不上,差个几秒到十几秒。

原因在于双方对"通话开始"和"通话结束"的定义不同。FreeSWITCH 的 CDR 中有好几个时间字段:created_timeanswered_timehangup_timebridged_time,含义各不相同。运营商计费通常从收到 200 OK 开始,到收到 BYE 结束。

建议: 计费以 answered_timehangup_time 的差值为准,并且在 CDR 中同时记录 SIP 信令的原始时间戳,方便和运营商对账时有据可查。


坑八:日志太多磁盘写满了

这个坑说出来有点丢人,但确实发生过。FreeSWITCH 默认日志级别是 DEBUG,在生产环境跑几天就把磁盘塞满了,然后整个服务直接挂掉。

解决方案: 生产环境调整日志级别为 WARNINGNOTICE,同时配置 logrotate:

xml 复制代码
<!-- console.conf.xml -->
<param name="log-level" value="warning"/>

调试阶段可以临时开 DEBUG,但千万记得改回来。另外强烈建议把 SIP 抓包(pcap)和日志分开存储,用 tsharksngrep 按需抓取,别用 FreeSWITCH 自带的 SIP trace 长期开着。


总结与建议

回头看这些坑,总结几条经验:

对接前一定要拿到运营商的详细技术文档,包括鉴权方式、编解码要求、号码格式规范、DTMF 方式、并发限制等。口头沟通的信息往往不够准确,白纸黑字最可靠。

sngrep 是调试 SIP 问题的神器,强烈推荐。它可以实时捕获 SIP 信令并以流程图的方式展示,比看原始日志效率高很多。遇到问题第一时间抓包,比猜来猜去强十倍。

搭建一套独立的测试环境,用 SIPp 等工具模拟运营商侧行为,把各种异常场景(超时、拒绝、编解码不匹配等)都跑一遍再上线。

最后,FreeSWITCH 的官方文档和社区虽然不算特别友好,但 wiki.freeswitch.org 和 GitHub Issues 里还是有很多有价值的信息,遇到问题值得翻一翻。

希望这篇记录能帮到正在踩坑的你。

相关推荐
在屏幕前出油2 小时前
02. FastAPI——路由
服务器·前端·后端·python·pycharm·fastapi
斌糖雪梨2 小时前
invokeBeanFactoryPostProcessors(beanFactory); 方法详解
java·后端·spring
上进小菜猪2 小时前
复杂 SQL 查询性能优化:深入解析 KingbaseES 的连接条件下推机制
后端
野犬寒鸦2 小时前
面试常问:TCP相关(中级篇)问题原因即解决方案
服务器·网络·后端·面试
北辰alk3 小时前
性能调优实战:金仓数据库连接条件下推原理与案例拆解
后端
Tzarevich3 小时前
别再信它“一本正经地胡说”了!用 RAG终结大模型“幻觉”
后端·langchain·llm
悟空码字3 小时前
SpringBoot + 腾讯地图实战:打造全能型地理位置服务平台,开箱即用!
java·spring boot·后端
martinzh3 小时前
AI 再也不用截图点点点了!用一行命令让它直接画流程图
后端
顺风尿一寸3 小时前
Spring事务回滚探秘:从@Transactional到数据库连接的完整旅程
java·后端