做通信集成的人应该都有过这种经历:对方发来一份对接文档,里面写着“SIP服务器地址、端口、认证账号”,然后让你把自己的电话系统接进去。标题里的“freeswitc”一眼就能看出来是FreeSWITCH,一个开源软交换平台。在这个场景下,它的任务就是作为一台SIP服务器,对接其他的SIP服务器或SIP设备,把话路打通。
这事听起来不复杂,但我见过不少项目在这里卡住。有的是网关注册不上,有的是能呼出不能呼入,有的则是通了以后单通、杂音、断断续续。问题大多不在FreeSWITCH本身,而是对接前没想清楚拓扑关系、SIP信令里那些隐性的协议要求、以及媒体流的走向。这篇文章我会从实际干活的角度,把FreeSWITCH对接其他SIP服务器做电话接入的关键环节拆开讲,包括配置步骤、拨号方案设计、排错思路和上线后的维护,尽量做到拿到就能用。
1. 对接前先确定:你的FreeSWITCH在通话链路里扮演什么角色
1.1 两种典型的对接关系:给上游做终端,还是给下游做服务端
很多新手拿到“对接SIP服务器”这个需求,第一反应就是去找gateway配置,然后把IP填进去。但SIP对接并不是只有一个方向。你在链路里扮演的角色,决定了你要改profile、建gateway、还是直接配dialplan即可。
第一种场景:FreeSWITCH作为上游SIP服务器的终端,也就是“下游节点”。这种情况下,你的FreeSWITCH需要向对方的SIP服务器发起呼叫,或者接收对方转来的呼叫。典型应用是公司分机要通过一条SIP中继打出去,或者两个独立的电话系统要互联。这时你需要配置gateway,并处理注册、认证、路由前缀。
第二种场景:FreeSWITCH作为SIP服务端,接收其他SIP服务器或终端的接入。典型应用是分机注册、IP话机接入、或者客户自己的SIP服务器把呼叫送给你的FreeSWITCH,再由FreeSWITCH落地到内部的分机或呼叫中心。
这两种角色并不是互斥的,一个FreeSWITCH可以同时扮演两种角色。内部的分机通过它打外线,外部的中继呼进来落到分机。但理解角色区别很重要,因为配置位置不一样。作为终端方,核心配置在gateway;作为服务方,核心配置在SIP profile的接入策略和dialplan的呼入匹配。
我见过最典型的错误,是把上游服务器的地址填到gateway里,却忘了FreeSWITCH要主动向前呼叫时,gateway才生效;而对方把呼叫送到你的公网地址时,匹配的是你的SIP profile收到的INVITE请求,两个流程在dialplan里是两套完全独立的入口。
说白了,SIP对接就是“出口”和“入口”两个方向。出口对应gateway,入口对应profile加dialplan。先把这两个方向分清楚,后面就不会乱。
1.2 公网、内网和NAT环境下对接的差异
对接方式还受网络环境影响,这块决定了信令和媒体包能不能正确到达对端。
如果双方都有公网IP,那是简单的情况,SIP信令直接走UDP 5060(或自定义端口)就能互相看到,媒体流走RTP端口。你只需要确保防火墙放行对应端口,尤其在对接大流量中继时,RTP的端口范围要留够。
如果有一方在NAT后面,麻烦就来了。FreeSWITCH收到对端发来的INVITE时,带的是公网IP的Contact,如果FreeSWITCH回给私有IP,信令就永远到不了对端。解决思路有几种:一是把FreeSWITCH放到公网;二是做端口映射,并开启对应的ext-rtp-ip、ext-sip-ip参数;三是在NAT设备上做好SIP应用层网关的协同配合。实际项目里最推荐的方式,是给FreeSWITCH分配一个公网可达的IP,不要指望NAT穿透能解决所有问题,尤其是媒体部分。NAT穿透能够承载信令,但RTP媒体端口往往因为映射不够或者端口范围限制,导致单通。
如果两边都在内网,中间通过专线互通,那SIP信令和媒体都可以走内网路由。这种情况反而是比较稳定的,因为你不用考虑公网里的各种扫描和探测流量。
1.3 编码与协议选型:直接决定接通率
编码方面,G.711是SIP中继里最通用的,几乎所有的服务商都支持。G.729在带宽受限场景下很常见,但要注意授权问题,所以我一般会建议两边协商用G.711,或者让FreeSWITCH做编码转换。实际项目里我还遇到过上游只支持PCMA(alaw),而分机用的是PCMU(ulaw),如果不处理转码,就会出现“能信令握手成功但通话没声音”的情况。
协议栈方面,默认的SIP响应和UA字段通常不需要大改,但对接一些兼容性差的服务商时,可能需要调整inbound-codec-prefs、outbound-codec-prefs。另外,有些服务商要求严格校验From和P-Asserted-Identity,这种就要在gateway或者dialplan里重写主叫号码,而不是简单透传。
2. 网关对接前,先把FreeSWITCH的SIP基础配置调对
2.1 分清internal与external profile的使用边界
FreeSWITCH默认有两个SIP profile:internal和external。内部注册和内部呼叫走internal,外部对接走external,这是最常用的思路。我见过有人把外部中继的peer信息直接加到internal里,虽然没有错,但会把内部的ACL、认证策略全部暴露给不可信的外部流量,安全隐患很大。
如果你要对接的SIP服务器是对端主动呼叫过来,可以在external的gateways里配置一个远端peer,也可以直接在dialplan入口里判断来源IP。如果是对端要求你主动注册到它那边,那就需要建一个带认证的gateway。
external的监听端口默认是5080。很多服务商的文档默认要求对端访问5060,这时候要么改external的sip-port到5060,要么直接使用internal监听5060再配合ACL。我的习惯是,把internal留给内网注册,把external的端口改成5060用于外部中继对接,同时在internal和external的apply-inbound-acl上都做好白名单控制。
2.2 gateway配置:注册与非注册两种模式
对接SIP服务器,到底要不要“注册”到对方,取决于服务商的认证方式。有些服务商把对接方IP加入白名单,不需要注册即可呼出呼入;有些则要求提供账号密码进行注册,以此验证身份。
注册模式的gateway配置大致是这样:
<gateway name="carrier_gateway"> <param name="realm" value="sip.carrier.example"/> <param name="username" value="8001"/> <param name="password" value="YourPassword"/> <param name="register" value="true"/> <param name="expire-seconds" value="120"/> <param name="retry-seconds" value="30"/> </gateway>非注册模式一般长这样:
<gateway name="peer_gateway"> <param name="realm" value="sip.peer.example"/> <param name="register" value="false"/> <param name="proxy" value="sip.peer.example"/> </gateway>两点经验:一是注册模式下,realm一般填对方的域名或IP,username和password要和对方给的账号一致,不要把username想当然地写成自己的分机号;二是如果你除了注册之外还需要走主叫号码透传,很多服务商要求caller-id和注册账号一致,不能用分机号随意填,否则上游会拒信或改掉主叫。
2.3 用ACL把访问范围收紧:这是第一道防线
公网环境里的FreeSWITCH,每分钟都会收到一堆扫描流量,所以ACL一定要有。基础做法是在autoload_configs/acl.conf.xml里定义可信网络列表:
<list name="carrier_peer" default="deny"> <node type="allow" cidr="203.0.113.0/24"/> <node type="allow" cidr="198.51.100.7/32"/> </list>然后在externalprofile里引用:
<param name="apply-inbound-acl" value="carrier_peer"/>这样非白名单IP发来的INVITE会直接被拒掉,而不是进入你的dialplan。很多人图省事把ACL设为allow all,结果就是各种盗打。SIP盗打是真实存在的风险,网关上若配置了国际长途中继,一旦被人扫到并注册成功,话费损失很难追回。所以ACL不只是安全建议,是必须项。
3. 拨号方案:让呼叫按你的预期流动
3.1 分机打外线的路由写法
网关建好以后,分机要通过网关打外线,需要在dialplan里写一条路由规则。比如公司内部统一拨号规范:先拨9,再拨外线号码。
<extension name="outbound_via_carrier"> <condition field="destination_number" expression="^9(\d+)$"> <action application="set" data="effective_caller_id_number=${caller_id_number}"/> <action application="bridge" data="sofia/gateway/carrier_gateway/$1"/> </condition> </extension>这条规则的意思是:分机拨9加任意号码时,剥离9,把后面的号码放到网关carrier_gateway上呼出。sofia/gateway/是FreeSWITCH的内部协议地址格式,固定写法,不用改。
这里有个实际项目里容易翻车的地方:分机本身有自己的user/{分机号}路由,如果呼出路由放在了分机路由后面,或者没有设置合适的caller_id,那就会出现分机拨9却呼到了自己的语音信箱,或者直接被系统当成内部呼叫处理。所以dialplan的匹配顺序是有讲究的,一般来说呼出路由要放在分机路由之前,并且用^9(\d+)$这种明确的前缀规则来区隔。
3.2 外线呼入怎么落到内部分机
呼入流程不一样。对端发INVITE到你的external profile,dialplan根据destination_number去匹配。最简形式是对方把内部的分机号直接作为被叫号码送给你:
<extension name="did_to_user"> <condition field="destination_number" expression="^(\d{4})$"> <action application="answer"/> <action application="bridge" data="user/${destination_number}"/> </condition> </extension>如果外部线路送的是完整的电话号码(比如一个DID号码,如0755开头),而你内部用短号分机,就得做号码映射。两种做法:一种是用Dialplan里最简单的正则替换,把长号码映射成短号,再bridge到user/;另一种是用更结构化的方式,把DID与内部坐席关联。
另外,呼入的时候建议显式设置ring_timeout,避免电话一直振铃没人接。比如:
<action application="set" data="ring_timeout=25"/> <action application="bridge" data="user/${destination_number}"/>还有个细节是answer动作的位置。很多老手不写answer,因为bridge本身也会触发应答。但如果呼入路由后面要做录音、TTS、或者放回铃音,那先answer再串流程更稳妥。
3.3 号码清洗与编解码控制:细节决定体验
对接不同SIP服务器,最大的隐性差异就是号码格式。上游送过来的号码可能是+86开头的,可能是带0的,也可能是只带分机号。分机打出去也一样,有的中继要求带区号,有的要求去掉0。
我不建议在分组交换机里对号码格式做太多硬编码。更可控的做法是使用regex表达式做变量处理。比如把当前拨打的号码去0:
<action application="set" data="dialed_number=${regex(${destination_number}|^0(.+)$|$1)}"/>然后再用${dialed_number}去bridge。
编解码控制方面,在gateway上没有单独的codec参数时,实际生效的是profile层面的inbound-codec-prefs和outbound-codec-prefs。如果对端要求只有G.729但你的profile默认是PCMU,就会转码,或者协商失败。简单的办法是两边约定好编码,然后把FreeSWITCH的outbound-codec-prefs按顺序排列好。另外一个原则是:不要让媒体在FreeSWITCH里转来转去,转码除了消耗CPU,还会在某些场景下引发回声和延迟。能透传就透传,这是性能第一原则。
4. 联调排错:从“对方没收到”到“双方能说话”
4.1 呼出失败时先看这几层
对接联调的排错,我推荐按层级来看:第一层是FreeSWITCH有没有成功发INVITE;第二层是对方有没有回响应;第三层是认证有没有通过;第四层是媒体有没有通。
排查第一层的命令很直接:
sofia global siptrace on这个命令会把所有出入的SIP信令打到控制台,包括INVITE、100 Trying、180 Ringing、200 OK、ACK等。如果发INVITE都没看到,说明gateway状态有问题,或者路由没匹配到。
检查gateway状态用:
sofia status gateway carrier_gateway输出里的REGISTER状态如果是Registered,说明注册成功。如果Registration一直失败,重点看realm、username、password,以及对方服务器是不是在白名单模式下不接受注册。
认证失败还会出现401、403、485等响应。401是对方要求你认证;403是认证被拒;485是号码格式有问题。这几个状态码排错方向完全不同。
4.2 只能呼出、呼不进来的排查链路
这个问题在对接场景里出现频率极高。呼出正常说明网络是通的、账号是好的,问题几乎都出在“给对方送呼叫的入口”上。
排查链路一般是:先问对方有没有收到INVITE收包记录;如果在对方能看到,但立马收到401或403,那问题就和你在呼入profile里配置的认证有关;如果对方根本收不到,那就是网络入口问题——IP地址、端口、防火墙、ACL、NAT映射都要检查。
FreeSWITCH端能做的检查有这几个:一是sofia status profile external,看profile的监听地址和端口对不对;二是检查apply-inbound-acl有没有把对方的IP拒掉,直接在控制台看日志,如果出现Rejecting invite ... due to acl,就是ACL把对方拦了;三是确认没有开着inbound-codec-prefs导致协商失败。实际操作中,我遇到过很多次因为ACL配置太严,把对接方的IP给忽略了,结果呼入全部被拒。
4.3 单通、杂音、回声?问题大多出在媒体路径
信令通了、双声道却听不到声音,或者只有一方能听到,这是最磨人的问题。而单通的常见原因,是媒体RTP包没有回到正确的主机地址。
举个我处理过的案例:某项目中,服务商的SIP中继部署在公网,而FreeSWITCH在办公网内,出口做了端口映射。信令能通,呼叫也建立了,但接听方听不到对方讲话。查了tcpdump后发现RTP流发送目标是内网私有IP,对方根本没有路由可达。这就是典型的NAT媒体穿透问题。解决办法,是在SIP profile里面设置正确的对外IP:
<param name="ext-rtp-ip" value="203.0.113.10"/> <param name="ext-sip-ip" value="203.0.113.10"/>如果两个座位都在同一个局域网里,也可以用rtp-ip指定为内网地址,但要保证对方能路由到该地址。
回声和杂音的来源往往是转码或者网络丢包。先做ping测试和traceroute,看丢包率;再在RTP包里检查实际采用的编码和SSRC是否一致;最后的通用手段是打开回声消除的配置,让media_bug或mod_echo处理。
4.4 把SIP Trace变成日常习惯
联调结束以后,我会把sofia global siptrace off关掉,避免日志刷屏。但排查期间的SIP Trace信息价值极高,它能把呼叫流程从头到尾完整呈现出来,定位到具体是哪一个SIP头域出了问题。
还可以用抓包工具过滤SIP和RTP流量。常用过滤规则:sip、rtp、udp.port==5080、udp.port==5060。分析INVITE里的Contact头、Via头、SDP的c行和m行,往往能找到问题根源。
5. 对接上线后不能省的维护工作
5.1 公网环境下防盗打的几个硬措施
对接服务商一般都会提供公网地址,上线之后的持续安全维护非常关键。首先,不要使用默认的ClueCon控制台密码,这个是控制FreeSWITCH内部命令的通道,暴露给别人等于把路由和网关都交给对方。
其次,为所有gateway的注册账号设置强密码,并且定期更换。跟服务商对接时,要确认对方是否支持IP白名单模式的认证,如果支持就直接用白名单加高密码双保险,而不是只依赖注册。
第三,ACL白名单要定期梳理,把不再对接的IP段移除。尤其那些临时调试用的来源IP,调试完就要删掉,别留在大名单里放着不管。
最后,有空可以写个简单的监控脚本,定期统计一定时间窗口内的呼叫失败率、同时呼叫数、按号码段对账。如果出现异常高的话务峰值再配合告警,大部分盗打都可以在早期发现。
5.2 RTP端口与媒体路径的稳定性坑
FreeSWITCH的RTP端口默认是16384-32768这一段,端口数量多,但如果防火墙没有放行对应的UDP范围,呼叫就会在媒体加载时卡住。对接服务商时,要确认对方是否限制端口范围,然后据此调整你的RTP端口起始和数量:
<param name="rtp-start-port" value="10000"/> <param name="rtp-end-port" value="20000"/>这里有个比较少人注意的细节:每个并发通话会占两个RTP端口(一收一发),不是用一个端口。所以10000个端口可用时,最多支持并发5000路(理论值)。配置端口范围时要按实际并发需求来算。
如果媒体走了公网,建议在profile里开启rtp-timeout-sec,避免某些异常状态下占着媒体资源不放。这个参数控制的是RTP长时间没有收到包时,系统判定通话已结束的机制,合理设置能防止大量僵尸话路。
5.3 用fs_cli做日常健康检查
上线之后,运维不是装了完事。在FreeSWITCH服务器上常驻使用fs_cli检查状态,是最快捷的日常操作:
fs_cli -x "show calls" fs_cli -x "show channels" fs_cli -x "sofia status" fs_cli -x "sofia status gateway carrier_gateway" fs_cli -x "status"show calls看当前活动话路数,show channels看每个通话处于什么状态(Ringing、Answered、Hangup)。如果话路长时间卡在某个状态不释放,就要查是不是对端没有正常收ACK,或者媒体流中断导致FreeSWITCH认为还在通话中。
比较实用的建议是写一个定时任务,每分钟取一次show channels和sofia status gateway,把关键指标存下来。哪天忽然接到“电话打不通”的反馈时,先看数据,再上控制台,而不是一上来就抓着日志文件翻。
最后再分享一个我自己的操作习惯:每次调整profile或gateway配置前,先在fs_cli里执行reloadxml或者干脆重启对应profile来验证,但生产环境我一般不会直接reloadxml,而是挑业务低峰期操作,改完立即抓一条真实呼叫看SIP Trace。语音系统这种东西,出了故障用户往往比日志还急,多一些事先的监测和验证,就能少一些临场救火。