ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FreeSWITCH呼入呼出配置实战:SIP中继与拨号计划避坑指南

FreeSWITCH呼入呼出配置实战:SIP中继与拨号计划避坑指南

简介:这份文档面向使用 Freeswitch 搭建 VoIP 通信平台的运维与开发人员,聚焦呼入呼出路由配置这一关键环节,帮助读者理清拨号计划、SIP 中继与对等中继模式下的外呼实现思路。资源包内共 1 个 doc 文件,约 221KB,内容围绕 Freeswitch 整体架构展开,涵盖程序启动流程、消息分发机制、mod_sofia 模块组成与启动过程等核心知识点,并配有术语定义与条目编号规范,便于按模块查阅。文档以个人学习总结的形式梳理了系统介绍与组件架构,适合作为理解 Freeswitch 内部运行机制、排查路由配置问题的参考材料。目前已有 6455 人学习下载,对需要掌握呼入呼出路由与中继设置的初中级读者具有一定参考价值。

1. 从一次“能注册不能呼出”的翻车说起

上周帮一个做客服系统的朋友排查问题,FreeSWITCH 装好了,SIP 话机注册正常,内部分机互拨也没毛病,但一拨外线就静默挂断,日志里连个像样的报错都没有。这种“能注册不能呼出”的场景,几乎是每个刚接触 FreeSWITCH 的运维都会踩的坑——问题不在安装本身,而在呼入呼出路由和 SIP 中继的配置没对齐。FreeSWITCH 这套软交换环境在呼叫中心、企业 PBX、语音通知系统里太常见了,装不好轻则单通、回铃音丢失,重则整个中继打满导致业务停摆。这篇笔记面向的是已经装完 FreeSWITCH、正卡在路由配置上的开发者,从版本选择、模块加载、拨号计划到中继参数,一步步把呼入呼出跑通,最后给出验证方法和排错思路。我用的环境是 FreeSWITCH 1.2.7 这个老版本做参照,新版 1.10.x 在目录结构和模块名上有差异,文中会标注。

2. 装前把环境列清:版本、模块与目录约定

2.1 版本选择与平台差异

FreeSWITCH 的版本跨度很大,1.2.7 是很多老项目还在跑的稳定版,1.6 之后目录结构做了调整,1.10.x 是目前社区维护的主力。选版本的核心原则是:中继网关和 SIP 话机厂商的兼容性优先于版本新旧。我一般会先确认网关设备支持的 SIP 协议细节(比如是否支持 RFC 2833 DTMF、是否强制要求 PRACK),再决定用哪个 FreeSWITCH 版本。1.2.7 在 Windows 和 Linux 下都能跑,Windows 平台的模块文件是mod_sofia.dll这种形式,Linux 下是mod_sofia.so,配置文件路径统一在安装目录的conf/下。跨平台支持是 FreeSWITCH 的设计目标之一,Windows、Linux、BSD、Solaris 的 32 位和 64 位平台都在支持范围内,但生产环境我建议用 Linux,Windows 下模块加载和路径处理容易出玄学问题。

安装方式上,源码编译能精确控制模块,包管理器安装省事但模块裁剪不可控。源码编译的关键是modules.conf,这个文件决定了哪些模块会被编译进系统。默认配置里mod_sofia是启用的,但如果你用的是精简版安装包,很可能这个模块根本没编进去,后面配中继时就会发现sofia命令不存在。

2.2 核心模块与 mod_sofia 的加载关系

FreeSWITCH 的架构是核心加模块,核心提供通用函数和状态机,模块增强功能。SIP 相关的功能全部落在mod_sofia上,它由 FreeSWITCH 的 SIP 应用层和底层的 Sofia-SIP 协议栈库组成。Sofia-SIP 本身分 NTA 和 NUA 两层,FreeSWITCH 的 SIP 应用直接和 NUA 打交道,NUA 再依赖 NTA 做事务处理。这个分层意味着:你配的中继参数最终是传给 NUA 的,而传输层用 UDP 还是 TCP 对应用层是透明的,所以配中继时不用纠结底层传输,重点在 NUA 层面的参数。

模块加载由conf/autoload_configs/modules.conf控制。启动时 FreeSWITCH 先加载集成模块(CORE_SOFTTIMER_MODULE、CORE_PCM_MODULE),然后遍历modules.conf加载配置的模块,再遍历post_load_modules.conf。如果这两个文件里配置的模块数为零,才会从模块目录加载所有 mod。这个逻辑很关键——很多人改了modules.conf但没重启,或者把mod_sofia注释掉了却以为还在加载,结果中继死活起不来。

<configuration name="modules.conf" description="Modules"> <modules> <!-- 日志类模块先加载,方便排查后续模块问题 --> <load module="mod_console"/> <load module="mod_logfile"/> <!-- SIP 端点模块,呼入呼出全靠它 --> <load module="mod_sofia"/> <!-- 拨号计划相关 --> <load module="mod_dialplan_xml"/> </modules> </configuration>

这段配置里,mod_console和mod_logfile放最前面是为了让后续模块的加载日志能落盘,出问题时至少有日志可查。mod_sofia是 SIP 端点,没有它就没有任何 SIP 能力。mod_dialplan_xml负责解析 XML 拨号计划,呼入呼出的路由规则都写在拨号计划里。改完这个文件必须重启 FreeSWITCH,热加载模块虽然支持,但mod_sofia这种端点模块热加载容易残留旧状态,我一般直接重启。

2.3 目录结构与配置文件分布

FreeSWITCH 的配置目录结构在 1.2.7 和 1.10.x 之间差异不大,核心目录包括:conf/autoload_configs/放自动加载的模块配置,conf/dialplan/放拨号计划 XML,conf/sip_profiles/放 SIP 配置文件,conf/directory/放用户目录。呼入呼出路由的配置主要落在dialplan/和sip_profiles/两个目录。

sip_profiles/下的internal.xml和external.xml是两个默认的 SIP profile,分别对应内部分机和外部中继。呼入路由的入口在dialplan/public.xml,外呼路由的规则在dialplan/default.xml。这个分布不是随意的——public上下文处理来自外部中继的呼叫,default上下文处理内部注册用户的呼叫。搞混这两个上下文,就会出现“外线打进来进了内部拨号计划”或者“内部分机拨外线走了 public 上下文”的错乱。

3. 配 SIP 中继:对等中继模式的参数取舍

3.1 中继配置文件的骨架

SIP 中继是 FreeSWITCH 和网关设备之间的桥梁。对等中继模式(peer trunk)下,FreeSWITCH 不向网关注册,而是网关把呼叫直接送到 FreeSWITCH 的 IP 和端口,FreeSWITCH 也直接把呼叫发往网关的 IP 和端口。这种模式省掉了注册开销,但要求双方的 IP 和端口必须精确匹配。

中继配置写在conf/sip_profiles/external.xml里,核心是<gateway>节点。下面是一个对等中继的最小配置:

<gateway name="gw_pstn"> <!-- 网关地址,填对端设备的实际 IP --> <param name="proxy" value="192.168.10.1"/> <!-- 对等模式不注册,设为 false --> <param name="register" value="false"/> <!-- 呼出时携带的主叫号码,按网关要求填 --> <param name="caller-id-in-from" value="true"/> <!-- 传输协议,网关支持 UDP 就用 UDP,减少握手开销 --> <param name="transport" value="udp"/> <!-- 认证用户名和密码,对等模式有些网关不校验,但填上更稳妥 --> <param name="username" value="freeswitch"/> <param name="password" value="your_password"/> <!-- 呼出前缀,网关要求加拨特定号码时用 --> <param name="prefix" value=""/> </gateway>

proxy是对端网关的地址,对等模式下这是唯一必须精确匹配的地址。register设为false是对等中继和注册中继的分水岭——注册中继需要 FreeSWITCH 主动向网关发起 REGISTER 请求,对等中继则是网关主动把呼叫推过来。caller-id-in-from控制主叫号码放在 From 头还是 P-Asserted-Identity 头,不同网关要求不同,配错了会出现“对方看不到主叫号码”的问题。transport选 UDP 还是 TCP 取决于网关能力,UDP 延迟低但不可靠,TCP 可靠但握手开销大,语音场景一般用 UDP。

3.2 呼出路由:从拨号计划到网关

呼出路由的本质是:用户拨了一个号码,拨号计划匹配到这个号码,然后通过bridge应用把呼叫送到中继网关。拨号计划写在conf/dialplan/default.xml里,下面是一个典型的外呼规则:

<extension name="outbound_pstn"> <!-- 匹配以 0 开头的号码,0 是外线前缀 --> <condition field="destination_number" expression="^0(\d+)$"> <!-- 把前缀 0 去掉,取后面的实际号码 --> <action application="set" data="effective_caller_id_number=1001"/> <!-- 通过 gw_pstn 中继呼出,$1 是正则捕获的号码 --> <action application="bridge" data="sofia/gateway/gw_pstn/$1"/> </condition> </extension>

destination_number的正则^0(\d+)$匹配以 0 开头、后面跟数字的号码,$1捕获的是去掉 0 之后的实际号码。effective_caller_id_number设置主叫号码,这个号码会出现在网关看到的 From 头里,填错了对方会拒接。bridge的格式是sofia/gateway/网关名/号码,网关名必须和external.xml里<gateway name="...">的名字一致,写错了会报gateway not found。

这里有个容易翻车的点:拨号计划的上下文。default.xml属于default上下文,只有内部注册用户发起的呼叫才会进这个上下文。如果外呼是从其他上下文发起的(比如从 public 上下文转过来的),这条规则不会生效。排查时先看日志里的context字段,确认呼叫落在哪个上下文。

3.3 呼入路由:public 上下文的处理逻辑

呼入路由处理的是从网关进来的呼叫。网关把呼叫送到 FreeSWITCH 的 external profile 后,呼叫进入public上下文,由conf/dialplan/public.xml处理。典型配置如下:

<extension name="inbound_pstn"> <!-- 匹配网关送来的被叫号码,这里假设网关送的是完整号码 --> <condition field="destination_number" expression="^(\d+)$"> <!-- 转接到内部注册用户 1001 --> <action application="transfer" data="1001 XML default"/> </condition> </extension>

transfer应用把呼叫转到default上下文的 1001 分机,XML default指定了拨号计划类型和上下文。这里的关键是:public 上下文只负责“接进来”,具体转给谁、怎么振铃,交给 default 上下文处理。这种分层设计让呼入路由可以灵活组合——可以先查数据库决定转哪个分机,也可以先放 IVR 再转人工。

如果网关送来的号码带了前缀或者格式不标准,正则要相应调整。比如网关送的是8613800138000这种带国家码的格式,正则就得写成^86(\d+)$,捕获后再做号码变换。号码格式对不上是呼入失败最常见的原因,日志里会显示No route to destination或者直接404。

4. 避坑与排查:五个真实踩过的坑

4.1 坑一:中继显示注册成功但呼出静默失败

现象:sofia status显示网关状态UP,但拨外线后呼叫直接挂断,日志里只有NORMAL_CLEARING,没有更多信息。

原因:对等中继模式下register设成了true,FreeSWITCH 向网关发了 REGISTER 请求,网关返回 200 让状态显示 UP,但网关实际不处理这个注册,呼出时 SIP 请求发过去被静默丢弃。对等中继和注册中继是两种模式,混用会导致状态显示和实际能力不一致。

解决:确认网关的工作模式,对等中继把register改成false,注册中继才设true。改完重启mod_sofia或整个 FreeSWITCH,再用sofia status gateway gw_pstn确认状态。

4.2 坑二:呼入能振铃但一接就断

现象:外线打进来,内部分机正常振铃,但一摘机通话立即断开,日志显示MEDIA_TIMEOUT或CODEC_NEGOTIATION_FAILED。

原因:编码协商失败。网关只支持 G.711 A-law,而 FreeSWITCH 的 profile 里配置的编码列表第一个是 G.729,协商时双方找不到共同编码。G.729 需要授权,没授权的 FreeSWITCH 根本编不了,但配置里写了就会参与协商。

解决:在external.xml的 profile 设置里把inbound-codec-prefs和outbound-codec-prefs改成PCMA,PCMU,确保 G.711 优先。改完用show codec确认可用编码列表,没有授权的编码不要写进配置。

4.3 坑三:拨号计划改了不生效

现象:修改了default.xml里的外呼规则,重启 FreeSWITCH 后新规则依然不生效,旧规则还在跑。

原因:拨号计划有缓存,mod_dialplan_xml加载后会缓存 XML 解析结果。只重启mod_sofia不会刷新拨号计划缓存,必须执行reloadxml或者重启整个 FreeSWITCH。另外,如果改的是public.xml但呼叫实际落在default上下文,规则当然不生效。

解决:改完拨号计划后在控制台执行reloadxml,然后用show dialplan确认规则已加载。排查时在日志里搜context=确认呼叫实际落在哪个上下文,上下文对不上就改对应的 XML 文件。

4.4 坑四:主叫号码丢失导致对方拒接

现象:外呼能接通,但对方网关拒接,或者接通后对方看不到主叫号码,日志显示403 Forbidden。

原因:主叫号码的传递方式不匹配。有些网关要求主叫号码放在P-Asserted-Identity头,有些要求放在From头,caller-id-in-from参数配反了就会导致号码丢失。另外,effective_caller_id_number没设置时,FreeSWITCH 会用默认值,默认值可能不符合网关要求。

解决:先抓包看 SIP 信令里主叫号码在哪个头,再对照网关文档调整caller-id-in-from参数。拨号计划里显式设置effective_caller_id_number,不要依赖默认值。抓包用tcpdump -i any -w call.pcap port 5060,然后用 Wireshark 分析。

4.5 坑五:多中继场景下路由串线

现象:配了两条中继,一条走 PSTN 网关,一条走另一个 SIP 服务商,结果外呼时随机走错线路,有时能通有时不通。

原因:拨号计划的bridge里写死了网关名,但多条规则的正则范围有重叠,先匹配到的规则生效。比如^0(\d+)$和^(\d+)$两条规则,后者会匹配所有号码,如果它排在前面,所有呼叫都会走它指定的网关。

解决:拨号计划的规则按精确度排序,越精确的规则越靠前。用break="on-false"控制条件匹配失败后的行为,避免规则穿透。多中继场景下给每条规则加注释说明适用场景,改的时候不容易搞混。

5. 装完怎么验证:从信令到语音的完整检查

配置改完不代表跑通,验证要分三层:信令层、路由层、媒体层。信令层看 SIP 消息是否正常收发,路由层看拨号计划是否匹配到正确规则,媒体层看语音是否双向通。我一般按下面的顺序走一遍。

先看模块和中继状态。控制台执行sofia status,确认externalprofile 是RUNNING,网关状态是UP或REGED(取决于中继模式)。如果 profile 没起来,检查modules.conf里mod_sofia是否加载,端口 5060 是否被占用。网关状态不对,检查proxy地址和register参数。

# 查看 sofia 整体状态 sofia status # 查看指定网关详情 sofia status gateway gw_pstn # 查看当前注册用户 sofia status profile internal reg

这三条命令覆盖了中继和分机的基本状态。sofia status输出里重点看RUNNING和UP两个状态词,任何一个不对都说明配置有问题。

然后验证路由匹配。用originate命令模拟一次外呼,不实际拨号,只看路由是否匹配:

# 模拟从 1001 拨 013800138000,只走路由不实际呼叫 originate user/1001 013800138000

如果路由匹配成功,日志里会显示bridge到sofia/gateway/gw_pstn/13800138000;如果匹配失败,会显示No route。这一步能快速定位是拨号计划的问题还是中继的问题。

最后验证媒体。实际拨一通电话,用uuid_record录制通话音频,确认双向语音都有声音:

# 对指定通话录音,确认媒体流正常 uuid_record <uuid> start /tmp/test_call.wav

录完用播放器听一下,只有单边声音说明媒体路由有问题,通常是 NAT 或编码协商导致的。双向都没声音但信令正常,检查rtp端口范围是否被防火墙拦截。

验证通过的标准是:sofia status全绿、originate路由匹配正确、实际通话双向语音清晰、DTMF 按键能被网关正确识别。这四项都过了,呼入呼出才算真正跑通。从那以后我每次配完中继,都会先用originate空跑一遍路由,再实际拨测,最后录音确认媒体,三步缺一不可。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进