1. FreePBX 12 通话 30 分钟被挂断,问题到底出在哪
FreePBX 12 上跑 SIP 分机,通话几乎卡在 30 分钟整点掉线,换成 IAX 中继却一点事没有——这个现象我遇到过不止一次。它本质上不是"网络断了",而是 Asterisk 的 chan_sip 通道在会话定时器(session timer)到期后,双方没有成功完成 re-INVITE 刷新,于是主动拆掉了这条 SIP 会话。SIP 协议里有个 RFC 4028 定义的会话定时机制,默认协商出来的 session-expires 常见就是 1800 秒,也就是 30 分钟。一旦刷新请求因为 NAT、路由或对端不支持而失败,通话就被判定为超时终止。
IAX 为什么没事?因为 IAX2 是 Asterisk 自家的中继协议,它把信令和媒体揉在一条 UDP 流里,自带心跳保活,不依赖 SIP 那套 re-INVITE 机制,所以同样的网络环境下它不会触发 30 分钟这个坎。这也解释了为什么很多人换 IAX 中继后"问题消失了",其实只是绕开了 SIP 会话定时器。
这篇内容适合正在用 FreePBX 12 / Asterisk 1.8 到 13 这一代 chan_sip 的运维和集成同学。我会从 sip.conf 参数、抓包验证、日志排查一路讲到怎么用 TaoToken 的统一 API 通道去核对整条调用链路,把"30 分钟挂断"这个触发条件真正复现出来,而不是靠猜。核心检索词就三个:FreePBX SIP 30 分钟自动挂断、chan_sip session-expires、Asterisk 会话定时器排查。
先说结论方向:绝大多数情况下,把 session-expires 调大、同时确保 NAT 保活参数正确,就能解决。但如果你的链路里还接了外部语音/AI 服务,那还得确认调用侧的超时是不是也在 30 分钟附近,两边叠加才是真凶。下面一步步来。
2. 动手前的前置准备:TaoToken 统一 Key 与 API 通道
在改 Asterisk 配置之前,我建议先把"外部调用链路"这条线理清楚。因为很多 FreePBX 场景不是纯内部分机通话,而是接了语音识别、TTS、AI 对话这类外部服务,这些服务往往通过 HTTP API 调用。如果调用侧的超时设置和 SIP 会话定时器撞在一起,排查就会非常混乱。这时候用一个统一的 API 通道来收敛 Key 和 Base URL,能省掉大量"到底哪个 Key 失效了"的扯皮。
TaoToken 在这里的角色就是一个统一的模型/API 接入层:你拿到一个 Key,配一个 Base URL,就能在多个模型和服务之间切换,不用为每个服务单独维护一套凭证。对 FreePBX 这种要串联多个外部能力的系统来说,统一通道意味着日志、超时、鉴权都能在一个地方对齐。
你需要准备的东西不多:
第一,一个可用的 TaoToken API Key。登录后在控制台的 API Keys 页面创建,注意 Key 只在创建时完整显示一次,复制好存到密码管理器里。
第二,确认 Base URL。TaoToken 的 API 入口是https://taotoken.net/api,这个地址在配置里会反复用到,别写错成带路径的变体。
第三,想清楚你要接的是哪类能力。如果只是验证链路通不通,用模型对话页面手动发一条请求最快;如果是长期跑编码或 Agent 类任务,那更适合用 Coding Plan;如果只是要核对调用是否成功,接入文档里有完整的请求示例。
我一般会先在模型对话里手动测一次,确认 Key 有效、返回正常,再去改 Asterisk 那边。这样能把"外部服务挂了"和"SIP 定时器到期"两个问题彻底分开。顺序很重要:先证明外部链路是通的,再去怀疑 SIP 层。
这里有个容易踩的坑:有人把 Key 直接写进 sip.conf 或者 dialplan 里,这是不对的。SIP 配置和 HTTP API 调用是两层东西,Key 应该放在你调用外部服务的那个脚本或 AGI/ARI 程序里,通过环境变量或独立配置文件读取,别和 Asterisk 主配置混在一起。
3. 可复制的 chan_sip 配置:session-expires 与 NAT 保活
现在进入正题。FreePBX 12 的 chan_sip 配置,图形界面入口在 Settings → Asterisk SIP Settings,右侧选 chan_sip,底部有个 Other SIP Settings 区域,可以追加自定义参数。但为了让你看得清楚,我直接给/etc/asterisk/sip.conf里对应的关键片段,你对照着改。
先看全局段[general]里和会话定时、NAT 相关的参数:
[general] context=from-sip-external allowguest=no alwaysauthreject=yes ; 会话定时器:把默认 1800 秒拉长到 3600 秒 session-expires=3600 session-minse=90 session-refresher=uac ; NAT 保活,防止中间设备提前回收映射 nat=force_rport,comedia externip=你的公网IP localnet=192.168.1.0/255.255.255.0 ; 让 Asterisk 定期发 OPTIONS 保活 qualify=yes qualifyfreq=30 ; 会话刷新相关 rtptimeout=60 rtpholdtimeout=300逐条解释一下为什么这么配。session-expires=3600是核心,它把协商的会话有效期从 1800 秒提到 3600 秒,直接跨过 30 分钟这个坎。session-minse=90是允许的最小会话间隔,防止对端协商出一个过短的值。session-refresher=uac表示由发起方负责刷新,多数场景下这样更稳。
nat=force_rport,comedia这两个参数对 NAT 环境至关重要。force_rport强制按实际来源端口回包,comedia让媒体流跟着信令走,避免 RTP 被 NAT 设备丢弃导致"单通"或提前挂断。externip和localnet必须填对,否则 Asterisk 在 SDP 里会写内网地址,对端根本回不来。
qualify=yes配合qualifyfreq=30是保活的关键:Asterisk 每 30 秒给分机发一个 SIP OPTIONS,既探测在线状态,也顺便刷新 NAT 映射。很多 30 分钟挂断的根因就是 NAT 映射在 30 分钟空闲后被回收,而会话定时器又恰好在这个点触发刷新,两边一撞就断。
分机段[1001]这类也要检查:
[1001] type=friend secret=你的密码 host=dynamic context=from-internal dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw nat=force_rport,comedia qualify=yes session-expires=3600改完配置后,用asterisk -rx "sip reload"重载,别直接重启服务,重载能保留现有注册状态。如果你在 FreePBX 图形界面里改,记得点 Submit 后再 Apply Config,否则改动不会落到实际配置文件。
这里提醒一句:session-expires是双方协商的,如果对端运营商或网关硬性只支持 1800 秒,你单方面调大也没用,得看 re-INVITE 是否成功。所以下一步必须抓包验证。
4. 验证请求与成功结果:抓包、日志与链路核对
配置改完不能只看"感觉好了",得用数据证明。我通常分三层验证:SIP 信令层、Asterisk 日志层、外部调用链路层。
第一层,抓 SIP 信令。在 Asterisk 服务器上执行:
tcpdump -i eth0 -s 0 -w /tmp/sip_capture.pcap port 5060通话持续到 30 分钟附近时停止抓包,然后用 tshark 过滤 re-INVITE:
tshark -r /tmp/sip_capture.pcap -Y "sip.Method == \"INVITE\"" -T fields -e frame.time -e sip.CSeq -e sip.to.user如果看到在 1800 秒附近有 re-INVITE 发出但对方回了 4xx/5xx,或者压根没有 re-INVITE,那就说明刷新失败,问题定位成功。正常情况下你应该看到周期性的 re-INVITE 且返回 200 OK。
第二层,看 Asterisk 日志。开一个控制台:
asterisk -rvvvvv然后观察通话到 30 分钟时的输出,重点找这几类关键字:
Retransmitting #1 (NAT) to ... Sending re-INVITE ... Got 200 OK for re-INVITE Timeout, tearing down session如果出现Timeout, tearing down session,基本就是会话定时器到期没刷新成功。配合sip show channels和sip show peer 1001看当前会话状态和 qualify 延迟:
asterisk -rx "sip show channels" asterisk -rx "sip show peer 1001"Status显示 OK 且Qualify延迟正常,说明保活没问题。
第三层,核对外部调用链路。如果你的 FreePBX 接了 AI 语音服务,用 TaoToken 的模型对话页面手动发一条请求,确认返回正常。然后在你的 AGI/调用脚本里,把 Base URL 指向https://taotoken.net/api,Key 用控制台创建的那个,跑一次完整调用,看日志里 HTTP 状态码是不是 200。这一步能排除"外部服务超时导致通话被上层逻辑挂断"的可能。
成功的结果长这样:通话稳定超过 30 分钟,抓包里能看到规律的 re-INVITE 和 200 OK,Asterisk 控制台没有 teardown 日志,外部 API 调用返回正常。三者都对上,才算真正修好。
5. 本篇常见报错排查:401、local proxy failed 与 OAuth
排查过程中你会撞到几个典型报错,我按真实遇到的顺序列出来,对照着看。
401 Unauthorized:这个最常见。如果是 SIP 层,多半是分机密码错或alwaysauthreject生效后对端没带正确凭证。如果是外部 API 层,那就是 TaoToken 的 Key 不对或没带上。检查方法:SIP 侧用sip show peer 1001看注册状态;API 侧确认请求头里Authorization: Bearer <你的Key>写对了,Key 没多空格、没过期。注意 Key 只在创建时显示一次,丢了就重新建一个。
local proxy failed:这个报错通常出现在调用外部服务时,本地代理或网络出口配置有问题。先确认服务器能直接访问https://taotoken.net/api,用 curl 测一下:
curl -sS -o /dev/null -w "%{http_code}\n" https://taotoken.net/api返回 200 或 401 都说明网络通,返回 000 就是网络层不通,检查 DNS 和路由。别在服务器上配任何来路不明的转发工具,直接走正常网络出口即可。
reading choices 相关报错:这类多半是解析外部服务返回的 JSON 时字段对不上,比如返回结构变了或返回了错误对象。打印完整响应体再解析,别直接取choices[0]。加一层判空和状态码检查,能省很多调试时间。
OAuth 相关报错:如果你用的是需要 OAuth 的接入方式,报错一般是 token 过期或 scope 不对。重新走一遍授权流程,确认回调地址和 scope 配置正确。用统一 Key 通道的好处就是这类鉴权问题集中在一处,不用满系统找。
排查顺序建议:先看 SIP 层(抓包 + 日志),确认 30 分钟挂断是不是会话定时器导致;再看外部 API 层(curl + 日志),确认调用链路正常。两层分开查,别混在一起猜。如果 SIP 层已经确认 re-INVITE 成功、通话还是断,那问题一定在上层业务逻辑,去查你的 AGI 脚本或拨号计划里的超时设置。
6. 把链路收敛到统一通道:长期编码与 Agent 场景的接入建议
修完 30 分钟挂断只是第一步。如果你的 FreePBX 还要长期跑语音 AI、自动外呼、Agent 类任务,那调用链路的稳定性比单次通话更重要。我的建议是把外部调用统一收敛到 TaoToken 这一层,Key 和 Base URL 只维护一份,超时、重试、日志都在调用侧统一处理。
具体做法:在你的 AGI 或外部服务调用脚本里,把 Base URL 固定为https://taotoken.net/api,Key 从环境变量读取,别硬编码。然后针对长通话场景,把 HTTP 客户端超时设得比 SIP 会话定时器更长,比如 SIP 设 3600 秒,HTTP 超时设 3700 秒,避免外部调用先超时把通话带崩。
如果你是要长期跑编码或 Agent 类任务,Coding Plan 更适合,它针对持续调用做了优化,不用每次重新协商。接入文档里有完整的参数说明和示例,照着配就行。验证模型是否正常,直接用模型对话页面发一条测试请求最快。
最后给你一个实用技巧:把 SIP 抓包和 API 调用日志打上统一的时间戳,出问题时按时间轴对齐看。30 分钟挂断这类问题,往往就是 SIP 层和 API 层两个超时在同一个时间点叠加,对齐时间轴一眼就能看出来。配置改完记得sip reload,API 侧改完记得重启调用脚本,两边都验证一遍再上线。