telephony42 完全指南:从零开始在 dn42 构建电话网络
注:此处的“完全”意为“较完全”,不作对前置知识和相关配置 100% 覆盖的担保,亦不保证读者在阅读完毕后能立即动手实现本文涉及的所有功能。
注:本文列出的示例配置为演示方便,在原配置上进行了一些修改,仅供参考,不保证能在简单的复制粘贴后立即正常工作。
先前的博客文章 在 telephony42 中配置 E.164 ENUM 提到了,我们在 dn42 上初步搭建了一套实验性质的去中心化电话网络。那时的 telephony42 还处于比较草台班子的实验阶段,诸多问题尚未达成共识,缺乏统一的规范。
经过社区漫长的讨论、妥协与重构,2026 年 6 月 20 日,telephony42 的 Registry Schema 提案 终于被合并。
这意味着 telephony42 终于脱离了测试服阶段,成为了 dn42 Registry 的组成部分。现在,任何 dn42 参与者都可以像注册 ASN 和 IP 段一样,在 dn42 Registry 里申请全网唯一的电话号码前缀了。
随着 Schema 的合并,大量新玩家不断涌入。对于精通 BGP 路由和 WireGuard 隧道的 dn42 NOC 们来说,拉起一条跨国隧道网络就是敲几行命令或写几段代码的事情;但面对上个世纪遗留下来的电信概念、复杂的 SIP 信令栈,以及 VoIP 领域的玄学问题时,往往会感到一头雾水,无从下手。
因此,本指南旨在取代之前关于 telephony42 的旧文,尽可能详细地解析如何在新的 tel.dn42 框架下,构建属于自己的电话网络。
基础概念
在实际配置自己的电话网络之前,必须先对要搭建的东西有一个大概的认识。
电话网络本质上就是一个具有独特路由规则的应用层网络。让我们认识一些马上就会用到的前置定义。
一些组件
- PBX (Private Branch Exchange - 私有交换机)
可以将其视作 VoIP 网络中的路由器,负责处理信令和路由,有时也处理语音数据。呼叫路由、分机接入、传递信令、语音信箱等各种各样的任务均能由其承担。
它有各种各样的软件和硬件实现,有的会自带 RJ11 电话接口,有的则允许通过软件插件或硬件模块扩展功能,并可通过硬件接口连接到其他交换网络。Asterisk、FreeSWITCH 均属于软件 PBX 的代表。 - B2BUA (Back-to-Back User Agent - 背靠背用户代理)
Asterisk 之类的 PBX 就是一个典型的 B2BUA。与仅转发信令的 SIP 代理不同,当 A 通过 PBX 呼叫 B 时,PBX 实际上是先以被叫的身份接听了 A 的电话,然后再以主叫的身份向 B 发起一通全新的电话,最后在内部把这两个独立的通道桥接起来。这使得 PBX 能随时介入通话过程,进行录音、转码、甚至强制掐线。 - Endpoint (终端设备)
任何可以发起或接收呼叫的设备。它可以是电脑上的软件电话应用、桌面的实体 IP 电话机、ATA、网页上的 WebRTC 电话,甚至是另一个网络的 PBX。 - ATA (Analog Telephone Adapter - 模拟电话适配器)
一个自带网口(RJ45)和电话线接口(RJ11)的设备。它本质上也是一个 Endpoint,作用相当于调制解调器,可以把传统的模拟电话机、传真机的模拟电信号,数字化为 SIP 报文和 RTP 媒体流。借助这个设备,即使是几十年前的上古拨盘电话,通过恰当的转换,也能接入最新最热的 telephony42 网络。
Registrar、SBC、Media Gateway、SIP Proxy 等细分概念,由于在本文中已被 Asterisk 这个 All-in-One 软件完全囊括,为了降低读者中途放弃的可能,暂不专门涉及。
一些协议
VoIP 的控制平面和数据平面通常是分离的,这也是抓包时最容易使人迷惑的地方。总的来说,搭建 telephony42 的电话系统,通常会接触到以下几种协议:
- 会话发起协议 (SIP - RFC 3261)
作为控制平面。采用纯文本格式,长得有点像 HTTP,包含 URI、头字段和正文。主要负责信令传输,例如传递呼叫的来源和目标、接通、挂断或拒接等,不负责传输具体的语音流。SIP 默认监听于 UDP/TCP 5060 端口(在报文较大时通常优先使用 TCP),TLS 加密状态下监听 TCP 5061 端口。 - 实时传输协议 (RTP - RFC 3550)
作为数据平面。当 SIP 握手与 SDP 协商完成后,双方会在协商好的高位动态端口(通常在 UDP 10000 至 20000 之间)激情互喷 RTP 数据包。这些包对延迟和抖动极其敏感。如果防火墙只放行了 SIP (5060) 但没放行 RTP,就会遇到 VoIP 史上最著名的故障: 单通(接通了但听不到声音)。 - 实时传输控制协议 (RTCP - RFC 3550)
与 RTP 伴生的控制协议。它也不承载任何媒体流数据,功能是在 RTP 邻近端口(通常为 RTP 端口 + 1)周期性地传输控制统计包,从而收集通话期间的丢包率、抖动、单向传输延迟等服务质量数据,为 PBX 或终端调整前向纠错及缓冲区提供依据。 - 会话描述协议 (SDP - RFC 4566/8866)
夹带在 SIP 报文体中的清单数据。它的作用类似于 BGP 的 Capability 协商。通过 SDP,双方互相宣告各自支持的音频编码、采样率,以及准备用来接收语音的 IP 地址和动态端口。
下面这个序列图展示了一个典型的跨网 SIP 呼叫建立与 RTP 媒体流传输的过程:
sequenceDiagram
participant Caller as 主叫设备 (A)
participant PBXA as 本地 PBX
participant PBXB as 对端 PBX
participant Callee as 被叫设备 (B)
Note over Callee, Caller: 控制平面交互 (SIP)
Caller->>PBXA: INVITE sip:[email protected] (含 SDP 提议)
PBXA->>Caller: 100 Trying
Note over PBXA, PBXB: ENUM DNS 查询与信令路由
PBXA->>PBXB: INVITE sip:B@PBXB_IP (含 SDP 提议)
PBXB->>PBXA: 100 Trying
PBXB->>Callee: INVITE (含 SDP 提议)
Callee->>PBXB: 180 Ringing (振铃)
PBXB->>PBXA: 180 Ringing
PBXA->>Caller: 180 Ringing
Note over Callee: 用户接听电话
Callee->>PBXB: 200 OK (含 SDP 应答)
PBXB->>PBXA: 200 OK (含 SDP 应答)
PBXA->>Caller: 200 OK (含 SDP 应答)
Caller->>PBXA: ACK
PBXA->>PBXB: ACK
PBXB->>Callee: ACK
Note over Caller, Callee: 数据平面交互 (RTP)
Caller-->>PBXA: RTP 语音流
PBXA-->>PBXB: RTP 语音流
PBXB-->>Callee: RTP 语音流
Callee-->>PBXB: RTP 语音流
PBXB-->>PBXA: RTP 语音流
PBXA-->>Caller: RTP 语音流
Note over Caller, Callee: 控制平面交互 (SIP)
Caller->>PBXA: BYE
PBXA->>PBXB: BYE
PBXB->>Callee: BYE
Callee->>PBXB: 200 OK
PBXB->>PBXA: 200 OK
PBXA->>Caller: 200 OK
除此之外,根据折腾程度,还可能接触到:
- 安全实时传输协议 (SRTP - RFC 3711)
RTP 本身是明文裸奔,沿途任何一个路由器抓个包就能还原出完整的通话录音,而 SRTP 可以对语音数据负载进行加密。在跨越不受信任的网络时,强制启用 SRTP 是保护隐私的重要手段。 - Inter-Asterisk eXchange Version 2 (IAX2 - RFC 5456)
这是 Asterisk 的私有协议。与 SIP 的控制与数据分离不同,IAX2 将信令和语音媒体流复用在同一个 UDP 4569 端口上,因此能够轻易穿透复杂的 NAT 环境,并且能够将多个通话捆绑在一个 Trunk 里进行传输以节省开销。如果两端都是使用 Asterisk 互联,采用 IAX2 实际上比 SIP 更为方便。 - STUN (RFC 3489/5389/8489) / TURN (RFC 5766/8656) / ICE (5245/8445)
解决 NAT 问题的三大协议族。STUN 服务器让内网设备知道自己映射的外部 IP 是什么;TURN 服务器提供中继,让通话双方强行连通;ICE 则是一个调度框架,负责在直连、内网、中继之间测试并选出最优路径。
一些概念
网络与路由
- Trunk (中继) 是两个 PBX 之间的连接通道。不同于只能单路通话的分机,Trunk 允许多个并发通话,是跨域路由的基础。这个概念与 VLAN 中的 Trunk 有一定相似之处。
- Dialplan (拨号计划) 是电话路由系统的本体。本质上是一坨按顺序执行的脚本规则,不仅能按照模式匹配路由呼叫,还能随意修改请求中的参数或执行自定义操作,十分灵活。
- ENUM (E.164 Number Mapping) 是一种自动发现路由的机制。当拨打目标是未知目的地的电话时,PBX 会根据号码查询 DNS 的 NAPTR 记录,查找到该号码对应的 SIP 目标地址(例如
sip:[email protected]:5060)。 - Context (上下文) 是 Asterisk 拨号计划的关键组件。可以把它理解为 VoIP 系统中的 VRF。它可以把不信任的外部呼叫丢进一个没有外拨权限的隔离上下文中,并且能够通过
Goto等语句实现在不同上下文间链式跳转。
身份与鉴权
- CallerID 又称来电显示,包含两个要素:主叫号码 (Num) 和 号码名称 (Name)。能够将主叫方的电话号码(有时包括名称)显示在被叫方的设备上。容易被伪造。
- DID / DOD (Direct Inward/Outward Dialing - 直接内呼/外呼)
- DID 允许外部呼叫者拨打分配的全网唯一号码直接绕过自动人工总机,直接让特定的分机响铃。
- DOD 指分机在向外发起呼叫时,PBX 强制在送出的信令中携带分配给该分机的特定外呼 CallerID(主叫号码),确保对方屏幕上能显示规范、可回拨的合法号码。
媒体传输
- Jitter Buffer (抖动缓冲区) 是接收端为了应对网络抖动(导致数据包到达时间不一致)而设立的缓冲机制。它会暂存几个语音包,然后以平滑、均匀的速率丢给音频解码器。设得太大延迟会变高,设得太小会导致声音卡顿。
- SIP ALG (应用层网关) 是家用路由器及部分商业路由器里最声名狼藉的功能之一。某些路由器厂商本意是想通过监听并篡改 SIP 报文里的内网 IP 地址来帮助穿透 NAT,但由于各家实现极其糟糕,它往往会把原本正确的 SIP 报文改得面目全非,导致电话注册失败、单通或中途断线。
- DTMF (Dual-Tone Multi-Frequency - 双音多频 - RFC 2833)
在通话建立后,用户在电话机上按键产生的信号的传输机制。在 VoIP 领域主要有三种传输模式:- Inband:直接作为普通的模拟音频混合在 RTP 语音流中传输,即为我们常听到的按键音。由于此方式下的音频信号在经过 G.729 或 Opus 等高压缩率编码后,极易发生波形失真,从而导致接收方解析失败。
- RFC 2833 / Telephone-Event:将按键事件打包为专用的带外 RTP 包发送,不受音频压缩编码的影响,是目前 IP 电话的主流配置。(DTMF 的 RFC 2833 实际上已被 RFC 4733 取代,虽然业内仍习惯称之为 2833)
- SIP INFO:通过控制平面的 SIP 消息包传递按键事件,不占用 RTP 通道。
一些编码
在 SDP 协商中,编码器的选择决定了通话质量与带宽消耗。
传统电信网络几十年如一日地使用 G.711(alaw/ulaw),它只支持 8kHz 采样率,这就是为什么在 PSTN 拨打某些号码的时候总觉得声音沉闷、待机音乐是全损音质。
在 telephony42 这个纯 IP 网络中,我们完全可以抛弃历史包袱,将 Opus 设置为语音类呼叫的最高优先级编码器。Opus 支持全频带(48kHz)音频采样,在跨国通话的极高丢包率下也能保持声音清晰。
常见的 VoIP 编码器对比:
| 编码器 | 采样率 (kHz) | 典型比特率 (kbps) | 打包间隔 (ptime) | 丢包抗性 | 压缩特征 | 带宽 |
|---|---|---|---|---|---|---|
| G.711 (alaw/ulaw) | 8 | 64 | 20ms | 极差 | 无压缩 | 窄带 |
| G.722 | 16 | 64 | 20ms | 较差 | 低压缩 | 宽带 |
| G.729 | 8 | 8 | 20ms | 一般 | 高压缩 | 窄带 |
| Opus | 8 - 48 (自适应) | 6 - 510 (可变) | 2.5 - 60ms | 极强 | 可变压缩 | 可变 |
- G.711 (alaw / ulaw) 是全球 PSTN 网络的绝对标准。毫无压缩可言,占用带宽极大,音质仅能保证听个响,但由于不经过压缩,CPU 占用极低,且对 in-band 传真和按键音支持最好。欧洲与中国使用 alaw,北美与日本使用 ulaw。
- G.729:极度节省带宽的语音编码,目前专利已过期。能在 8kbps 的极低带宽下提供类似 G.711 的音质,但声音会因为高度压缩,带有明显的机器味。
- G.722:宽带语音的先驱。只需要和 G.711 一样的带宽,就能让声音变得饱满、人声细节完整。目前多数现代 IP 话机都能原生支持。
- Opus:VoIP 领域的集大成之作。支持从 8kHz 到全频带 48kHz 的动态无缝切换,还能根据网络拥塞情况自动调整比特率,并且内置了极其强大的前向纠错机制。
- H.264 / VP8 / AV1:VoIP 同样支持视频。如果使用带有摄像头的 IP 视频话机或软电话进行视频通话或视频会议,它们大多使用这两种编码进行 SIP 视频会话建立。
从 e164.dn42 到 tel.dn42
dn42 的某些早期的文档中可能会提到 e164.dn42 和 +424 前缀。然而,现在社区已全面使用新的 tel.dn42 和 +042 标准。这背后有着众多考量和妥协。
E.164 编号计划
E.164 是由国际电信联盟(ITU)制定的国际公用电信编号计划。它规定了全球统一的号码格式(国家代码 + 用户号码),总长度严格限制在 15 位数字以内。

ITU 是一个极其强势且保守的政府间国际组织。在 IP 网络世界,IETF 慷慨地保留了 RFC 1918 等私有 IP 地址池;但在传统电信世界,ITU 从未、也拒绝为任何非政府的社区网络、开源实验平台或 Overlay 网络保留任何可自行使用的前缀。
由于 ENUM 的 E 指 E.164 号码,而 telephony42 的号码并非由 ITU 管理,只是使用与 E.164 相同的格式而已,因此本文后续提到的 telephony42 中的 ENUM 严格意义上并不能被称作 ENUM(至少 RFC6116 认为不能这么叫)。
但是,在本文中,为了理解方便,我们暂且假装没见到过这个 RFC。
e164.org 事件
历史上发生过一起著名的 e164.org 与 ITU 的争执:
早在 2004 年,社区 ENUM 目录服务
e164.org启用了当时全球尚未分配的+882 99号段。旨在获得足够用户基础后正式申请这一号段。然而,ITU 发现此事后大为震怒,不仅向联合国投诉,还向澳大利亚政府及通信管理局(ACA)施压,要求派人找 e164.org 的管理者进行约谈。但由于 e164.org 只提供基于 DNS 的号码目录,并不在公共网络上劫持流量,此事最终不了了之。
随后,ITU 采取了极具对抗性的反制手段:他们突然将
+882 99号码段正式分配给了一家商业电信公司。此举导致 e164.org 与 PSTN 网络发生了不可避免的路由冲突,从而迫使社区陷入混乱之中。
+424 的潜在冲突
在 telephony42 早期,参与其中的网络曾广泛占用 +424 作为前缀。逻辑很简单:+42 曾是捷克斯洛伐克的国家区号,国家解体后,+421 分配给了斯洛伐克,+420 分配给了捷克,而 +424 至今在 ITU 的数据库中处于未分配状态。
此外,大家觉得这个前缀非常酷!
但前车之鉴历历在目:如果继续占用 ITU E.164 的预留编号空间,一旦 ITU 某天将 +424 分配给某个新兴国家或跨国卫星网络,telephony42 将迎来与 PSTN 路由的碰撞。
tel.dn42 与私有前缀
因此,经过社区的多轮辩论,最终确定了具备一定防御性的新标准:
- 不再使用
+424前缀,新的 telephony42 将迁移至+042前缀。在 ITU 的 E.164 规范中,国家区号绝对不可能以0开头。因此使用+042从标准层面上避开了与 ITU 路由发生冲突的任何可能性。 - 停用模仿
e164.arpa的e164.dn42,后续也不再覆盖e164.arpa域名,转而启用完全由 dn42 管理的tel.dn42根域。大部分 PBX 软件都支持设置自定义 ENUM 域名。
通过这些改动,telephony42 获得了某种程度的独立性与合规性:虽然在编号格式上借鉴 E.164 的结构,但我们在 tel.dn42 内部运行的是另一套不受 ITU 管控的体系。
telephony42 的编号规则
目前的 telephony42 强制使用 +042 作为前缀。标准格式如下,PBX 之间的呼叫的 CallerID 号码必须按照此格式:
+042-[N]-[XXXX]-ext
为了保持与老旧硬件及 E.164 标准的兼容性,号码总长度依然不能超过 15 位。去掉 +042 和 5 位的 ASN 映射/自定义前缀,剩下留给内部分机号 (ext) 的长度最多不能超过 7 位。
在典型的 telephony42 场景下,参照 E.164 规范可以做如下对应(仅作格式对应,实际意义并不相同):
- International Dialing Prefix / 国际字冠:
+- Country Calling Code / 国家代码:
042- National Access Code / 国内长途字冠:
[N]
未来如果 telephony42 有了互联的其它电话网络,也许能算作长途- NDC / STD / 国内目的地代码 / 长途区号:
[XXXX]
实际操作中与前面的[N]合起来可以定位到具体的某个 PBX- Subscriber Number / 用户号码:
Ext
其实是由 Base Number / 基本号 和 Extension / 分机号 组成,由于我们大多使用 PBX 架构,此处在大部分情况下等同于分机号
| 前缀分配规则 | 状态 | 用途说明 |
|---|---|---|
+042-{0,1} | 保留 | 保留给系统或管理用途。 |
+042-2 | 开放 | 自定义前缀。可自由提交申请。 |
+042-4 | 开放 | AS 号码映射。AS424242XXXX 持有者可将 AS 结尾的四位数字映射为前缀。 |
+042-{3,5-9} | 保留 | 为未来的外部互联和空间扩展保留。 |
注册 telephony42 前缀
注册号码前缀与注册 IP 段一样,去 dn42 Registry 仓库克隆代码,在 data/telephony/ 目录下创建一个 RPSL 格式的文件即可。具体流程在 dn42 Wiki 的 Getting Started 页面也有所记载。
值得注意的是,除非有充分且合理的理由,一个 MNT 应当只申请一个前缀。此外,目前也不允许注册 PSTN 的既有电话号码(非
+042前缀,如运营商提供的家里的座机、手机号)。
申请 +04240000 段的 data/telephony/+04240000 文件示例:
1 | |
为了提供更安全的验证,同时防止 DNS 劫持,推荐加入
ds-rdata字段并配置启用 DNSSEC 支持。
在向 Registry 提交 PR 前,以本示例为例,需要前往 ns{1,2}.foo.dn42,即权威 DNS 上配置区域 0.0.0.0.4.2.4.0.tel.dn42.(把申请的号码倒过来写并逐级以点分隔,去掉加号,详见上一篇博客),并在其中编写 NAPTR 记录。
1 | |
配置权威 DNS 之后,记得在本地验证 dig NAPTR 2.1.0.0.3.0.8.0.4.2.4.0.tel.dn42 是否能正确返回对应的记录。
Asterisk 环境搭建
在开源软交换平台中,有几种比较受欢迎的选项:
- FreePBX:本质上是套壳了 Asterisk 的 Web 面板。提供了较美观易用的 WebUI 和开箱即用的环境。然而它也预设了几千条 telephony42 永远用不上的规则,对于需要底层配置的 NOC 来说,用它就像是在用宝塔面板,不够纯粹。
- FreeSWITCH:性能较高的企业级软交换平台,常用于呼叫中心高并发场景。使用冗长的 XML 作为配置语言,学习曲线极度陡峭。不推荐一开始就入坑。
- 原生 Asterisk:本文使用的方案。通过编写人类易懂的
.conf配置文件,能清晰地理解每一通电话是如何在系统中流转的。
安装基础环境
Debian 官方仓库提供了非常完善的 Asterisk 打包,无需手动编译。
1 | |
使用 sngrep 进行 Debug
VoIP 信令分析极为繁琐,直接用 tcpdump 看文本会使人崩溃,而 wireshark 对于这个场景而言又过于重量级。
sngrep 是一个专用于 SIP 的 TUI 抓包与可视化工具,它能将杂乱的 SIP 报文还原成清晰可读的时序图。
当遇到呼入失败或单通问题时,在终端直接执行 sngrep -c,就能够以 TUI 时序图的方式展示所有的 SIP 会话。据此可以直观地看到 INVITE 请求是否送达、SDP 报文中携带的 IP 地址是否有误,以及是哪一方发出了 BYE 或是 40X 拒绝状态码。
下图为通过 sngrep 捕获呼叫,并以列表方式展示:
1 | |
下图展示了一通正在通话中的呼叫的 sngrep 时序图:
1 | |
注:实际终端中,状态码、请求和其它重要元素会以不同颜色高亮显示,比上述展示的效果更加直观。
防火墙与安全配置
绝对不要把 SIP 端口裸露在 IANA 网络的 IPv4 上!
绝对不要把 SIP 端口裸露在 IANA 网络的 IPv4 上!
绝对不要把 SIP 端口裸露在 IANA 网络的 IPv4 上!
IANA 掌控之下的网络是残酷且野蛮的。稍有不慎暴露出 SIP 端口,不出一个小时,全世界数以万计的扫描器就会开始对你的 PBX 进行高频爆破 (通过大量 INVITE / REGISTER 请求)。
一旦运气不佳或配置失误导致分机密码被猜中,不法分子就会利用受害者的 PBX 疯狂拨打古巴、索马里等高费率国家的国际长途(这种行为被称为 Toll Fraud)。如果你恰好通过 SIP Trunk 连接了 PSTN 的 VoIP 提供商,你将会在一夜之间倾家荡产(账单可能高达数千美元,到时候连 dn42 基金会都救不了你了)。即使密码未被攻破,高并发的垃圾信令也会使 Asterisk 的日志迅速膨胀,同时服务器的 CPU 与网络带宽也会被榨干。
因此,对于仅接入 telephony42 的 PBX,推荐仅在 dn42 网络监听 SIP 端口。如果身在户外,想用手机连回家接打电话,请使用 WireGuard 等 VPN 接入自己的内网后,再进行连接;或者在防火墙上单独为接入设备的外部 IP 添加白名单。
目前很多软电话 App 为了在自身处于后台时节约手机电量、或避免后台进程被清理,会将注册凭据发送至远端服务器代为注册,当有来电的时候,通过 FCM(Android)/APNs(iOS) 等方式将呼叫通知发送到手机,App 打开后再进行重新注册并接听来电。
这种情况下,除了需要注意隐私问题外,为确保此机制能够工作,PBX 的 SIP URI 可能需要对该 App 的服务器可达(只需保证信令层面可达)。一般 App 厂商会公开其服务器的 IP 段以便用户配置防火墙。
此外,使用 Fail2ban 监控 Asterisk 日志(Fail2ban 官方有提供预置规则),并自动在 nftables 中拉黑多次认证失败的 IP 是一种防御方式,但这需要谨慎地配置以免误伤。
对于 RTP 语音流(默认值为 UDP 10000 - 20000,可在 rtp.conf 修改),可以相对宽松地放行 UDP,因为只有 SIP 握手成功后,Asterisk 才会真正在这些端口上收发语音流。
配置思路
进入 /etc/asterisk/ 目录,在大多数发行版下,可以看到默认包含了数十个示例配置文件。
对于构建一个轻量、高可控的 PBX 来说,完全可以把这些默认文件全删了,或备份到别的目录,仅保留或手动创建最小化配置集。
以下是笔者使用到的一些配置文件列表以及对应的功能解释。未列出的文件大部分可以是空白的,或只留必须写的部分,可以先启动 Asterisk 并观察日志,缺什么再补什么:
1 | |
命令行与配置重载
在配置和调试过程中,Asterisk 的命令行界面是必不可少的工具。在终端中输入 asterisk -r 命令即可连接到正在运行的 Asterisk 服务实例。
常用的命令有:
pjsip show endpoints:查看当前已配置的 PJSIP 端点及其在线状态。pjsip show registrations:查看 SIP 的注册状态。dialplan show:查看当前已加载的所有拨号计划。agi set debug {on,off}:打开或关闭 AGI Debug 输出,这在调试 AGI 程序时极为有用。
修改 Asterisk 的配置后,不需要重新启动整个 Asterisk 服务。可以使用 CLI 进行热重载:
asterisk -rx 'dialplan reload':重新加载拨号计划(extensions.conf)。asterisk -rx 'core reload':重载所有支持热重载的配置。
PJSIP 配置 (pjsip.conf) 解读
关于
chan_sip与chan_pjsip:
Asterisk 早期使用chan_sip作为 SIP 驱动,但由于其代码结构积重难返,现代版本(Asterisk 21+)中已彻底从代码库中移除chan_sip支持。现代 Asterisk 已全面转向新一代协议栈chan_pjsip,因此本文默认使用 PJSIP。
在 PJSIP 配置中,一个通信实体被拆分成了若干组件,需要通过相互引用把它们关联起来:
- Transport: 传输层。定义网络监听参数(绑定 IP、协议、端口、TLS 证书等)。
- Endpoint: 端点属性。定义会话的行为属性(允许的 Codec、绑定的拨号计划上下文、NAT 穿透开关、加密机制等)。
- Auth: 身份认证。存放用于注册或外呼验证的用户名和密码。
- AOR (Address of Record): 寻址规则。告诉 PBX 应该去哪里找这个端点,并配置注册过期时间、允许的最大同时在线设备数等。
- Identify: 身份识别。针对不使用密码、只使用静态 IP 互联的中继(Trunk),直接通过来源 IP 地址识别其绑定的 Endpoint。
此外还有负责注册的 Registration 和手动指定地址的 Contact 两个组件,在本文所述场景下不太常用。

以下配置可写入 /etc/asterisk/pjsip.conf,且由于每个分机都要写多个组件,配置会非常冗长。因此建议善加利用 Asterisk 的模板机制 (!),同时也可利用 #include 机制拆分文件。
全局与底层 Transport
如有条件,请务必启用 transport-udp6,许多 dn42 网络仅支持 IPv6,不启用将导致这些网络内的 PBX 无法访问,造成电话系统的割裂。启用 IPv6 后务必在 DNS 中添加对应解析,否则导致 ENUM 反向验证不通过。
1 | |
模板定义
内部分机 (Softphone / 硬件话机 / ATA 等)
这套模板将应用于所有注册在本地的、属于本网络的设备。
1 | |
手动指定的 SIP Trunk
如果需要和另一个 dn42 NOC 直接建立 PBX 间的 peer,即希望不查 ENUM,直接用 IP 对接,可使用这个模板:
1 | |
实例化网络节点
利用上面的模板,即可配置属于自己的 SIP 账号:
1 | |
添加一条手动指定的 SIP Trunk peer:
1 | |
ENUM 匿名兜底路由
对于整个 dn42 网络里那些未知的、通过 DNS ENUM 查到我们的 IP 并打过来的陌生来电,我们需要一个统一的兜底接入口。这类似于防火墙里的默认规则。
1 | |
注: PJSIP 使用最长前缀匹配(LPM)方案,会优先匹配掩码最长(最精确)的 Identify 规则。因此已知 peer (一般设置为 /128 或 /32) 会被优先截获,而不会被这里的规则匹配。
拨号计划(extensions.conf)解读
拨号计划是一门基于上下文块的、状态机跳转的脚本语言,思路类似于 Linux 里的 Netfilter。一通电话打进来,会先被设定进入某个特定上下文,然后进行多次匹配和上下文之间的跳转。
Asterisk 官方文档对 Dialplan 的说明和示例非常详尽,这里只介绍主要语法:
1 | |
对于号码匹配而言:
_:代表开启模式匹配。X:匹配 0-9 的任意单个数字。Z:匹配 1-9 的任意单个数字。.:通配符,匹配后面一个或多个任意字符。!:通配符,匹配零个或多个任意字符。
例如: _+042X. 代表模式匹配 +042 开头,且后面跟至少一个数字的号码。
此处还需要注意优先级的概念:
- 优先级是指同一个分机号内多个步骤的处理顺序。每个分机的处理都必须从优先级 1 开始,并按顺序递增执行。
- 为了避免在中间新增步骤导致重新编号,还可以使用
n优先级,它可以自动在上一个优先级的基础上加 1。 - 还有一种为优先级指定标签的做法
n(标签),在进行条件跳转(GotoIf)时相当有用。后续的许多示例配置都会用到此功能。
考虑如下的情况,对于匹配号码 100 的来电,会从上到下依次执行:
1 | |
此外,为了逻辑严密,笔者个人将整个系统的路由划分为三个层次:
拨入上下文 (context-*) -> 目标路由 (ext-*) -> 异常处理 (ext-invalid)。
以下内容均在 /etc/asterisk/extensions.conf 范围以内。同样地,建议善加利用模板和 include 机制。
拨入上下文
本地分机呼出(context-local)
自家设备呼出的电话直接进入到这个上下文里。我们需要在这里处理各种各样的用户拨号习惯。
此外,在不同层级的网络内部,可以不用加更高一级的前缀:
- 笔者网络内部使用定长 4 位分机号,因此,只要检测到输入了四位纯数字,可以直接跳转本地上下文,相当于 PSTN 中不用加区号的市内座机。
- telephony42 内部统一使用
+042前缀,因此我们可以默认不加+(或00)且长度不为 4 的号码,均为 telephony42 的内部号码,自动为其补全+042前缀并送入ext-peer跨网呼叫上下文。
1 | |
已知 Trunk 呼入(context-peer)
这里对应的是已知的 Trunk peer(或已验证的 ENUM 来电)呼入的情形,需要在正式转交跨网路由前进行一次清洗。
虽然我们要求在 PBX 间传递完整的 E.164 号码,但是考虑到一些 PBX 配置可能不规范,我们可以直接主动兼容常见的错误,例如把 + 写成了 00,或忘记了开头的 + 号。
1 | |
ENUM 呼入鉴权(context-enum)
在 SIP 协议里,任何人可以用一行代码随意伪造任何 CallerID。为了防止有人假冒其他人的号码打进来,当未知端点呼入时,我们需要实施类似于 uRPF 的反向校验机制。
大致流程为:
- 提取对方声称的来电号码
- 反查
tel.dn42的 DNS NAPTR 记录 - 查出这个号码合法的目标 IP 列表
- 对比这个 IP 是否等于此时此刻正在发包的源 IP
- 如果对不上,就是伪造的 Spoofed Call,进行拦截
用 Python/Go 写一个外部 AGI (Asterisk Gateway Interface) 脚本来做 DNS 解析比对,是很容易的。这在上一篇文章中有详细介绍,这里不多展开。
AD: tel42verifier 是笔者使用 Go 写的一个功能更全面的 ENUM 反向验证器,基本满足 telephony42 的需求,且可根据前缀指定不同的上游域名和 DNS,支持 DDDS 递归查询和 TXT 记录 CallerID 覆盖。
这里展示如何在 Asterisk 里调用它。
1 | |
目标路由
进入这些 ext-* 的上下文后,Asterisk 就要决定具体让哪一台话机响铃,或者怎样把呼叫路由到下一个网关。
内部网络寻址(ext-local)
1 | |
跨网路由(ext-peer 与 ext-enum)
当我们试图发起跨网呼叫时,会跳转到这个路由上下文上。
注意此处一定要使用完整的 E.164 号码,即带上 + 号进 ENUMLOOKUP,否则会匹配失败或导致对方拒接。
1 | |
关于 ENUM 呼出的超时时间与 SIP Timer B
在上面的配置中,我们将 Dial 的超时时间设置为了 60 秒。
根据 RFC 3261 17.1.1.2,SIP 协议有一个负责控制 INVITE 请求超时的机制叫做 Timer B。它的默认值为 32 秒。
在双栈网络中,如果 DNS 返回了对方的 IPv6 地址,但却由于各种原因导致 IPv6 无法建立会话,SIP 协议栈会等待 32 秒的 Timer B 超时,然后才会尝试回落到 IPv4,或返回失败状态。
如果 Dial 此时的超时设置为小于 32 秒的值,拨号计划会先于 SIP 协议栈掐断呼叫,导致永远无法触发 IPv4 的回退机制。
另外一种方法是全局覆盖 Timer B 设定,这可以在 pjsip.conf 中加入以下条目实现:
1 | |
异常状态处理(ext-invalid)
当 Dial 命令结束时,系统会生成变量 ${DIALSTATUS}(例如 BUSY 占线、NOANSWER 没人接、CONGESTION 网络拥塞)。我们在此处将冰冷的状态码化作温暖的语音播报反馈回去。
1 | |
IVR 交互式语音应答(ivr-*)
构建一个具有现代世界五百强企业质感的电话网络,自动语音菜单(IVR)是必不可少的。当陌生人拨入 IVR 后,可以播报一些信息,然后提示按键选择服务或执行一些自定义操作。
实际上 IVR 本身也是一种拨号计划的上下文:
1 | |
随后就可以在 ext-local 中,将指向特定号码的呼叫通过 Goto(ivr-main,s,1) 跳转过来,即可启用这套 IVR 系统。
由于在环境搭建阶段安装了 asterisk-espeak,如果不愿自己提供录制好的音频,也可直接在 Dialplan 里写 espeak("Text to speak.", any) 来实现 TTS,虽然实际效果一言难尽,很难听懂。
此外,也可以使用 AGI 脚本直接调用联网的 TTS 服务,此时需要保证返回的音频格式正确。
关于自定义音频与 TTS 的格式要求
如果希望获得更好的 TTS 效果,需要使用外部 AGI 脚本调用在线 TTS 服务,请务必注意,Asterisk 并不能像一般播放器那样解码所有格式的音频。
通常情况下,提供给 Playback 或 Background 等应用的音频文件必须严格符合以下参数:
- 窄带(G.711 / 8kHz)单声道,16 bit 位深,PCM 编码,文件扩展名 wav
- 宽带(G.722 / 16kHz)单声道,16 bit 位深,PCM 编码,文件扩展名 wav16
- 在安装了 mp3 文件格式支持后,也支持 单声道,8kHz 的窄带 mp3 文件
调用
Playback或Background等应用时,输入的文件名不能包含扩展名,Asterisk 会自动按照自身支持的编码器和 Endpoint 配置的规则读取合适格式的文件。
可通过 asterisk -rx 'core show file formats' 命令查询到所有支持的格式:
1 | |
实例推演
设想这样一个情景:
小猫猫 (分机
+042400010001) 拿起座机,意图拨打大狗狗网络的总机号+042400020001与大狗狗聊天,默认双方均按上述方案进行了配置。
- 小猫猫(
+042400010001)在自家电话上呼叫大狗狗(+042400020001)的短号400020001。- 当小猫猫拨出电话后,由于是本地通过用户名密码认证的 Endpoint,会首先被捕获到对应的 Endpoint。
- 由于 Endpoint 上规定了
context=context-local,小猫猫的电话首先被放到了context-local本地上下文中,同时被设置了对应的 CallerIDTinyNyaNya <+042400010001>。 - [context-local] 小猫猫的呼叫目标
400020001匹配上了context-local的 telephony42 兜底规则_X!,PBX 为其追加了+042前缀,成为了完整 E.164 号码+042400020001,以优先级 5 被扔进了ext-peer上下文。 - [ext-peer] 随后,
ext-peer查询前缀发现没有已知的 peer 规则,最终被 ENUM 兜底规则_+X.以优先级 5 送进了ext-enum上下文。 - [ext-enum] 呼叫目标匹配到了
_+042X.前缀,开始使用ENUMLOOKUP在tel.dn42上查询+042400020001,获得了 URIsip:[email protected]:5060,然后直接通过Dial将通话以INVITE的形式送出到大狗狗的 PBX。
- 这通电话到达大狗狗家的 PBX。
- 小猫猫的 PBX 送出的
INVITE抵达大狗狗的 PBX,被兜底的 ENUM Endpoint 规则按照 ip 地址捕获。 - 由于 Endpoint 上规定了对应的
context=context-enum,小猫猫的电话被放到了隔离上下文context-enum等待进一步检查。 - [context-enum] 该上下文首先检查了来电的 CallerID
+042400010001和呼叫目标的号码格式,确认无误。然后调用tel42verifier比对 SIP header 中的 IP 地址是否与小猫猫的号码在tel.dn42记录中对应的地址相匹配,以及如果可能的话,从 TXT 记录中获取 CallerID 覆盖之。最终小猫猫的来电通过了检查,被放进了context-peer上下文。 - [context-peer] 该上下文发现来电的目标号码
+042400020001是允许列表内的,予以放行,然后来电以优先级 5 被转移到ext-peer上下文。 - [ext-peer] 上下文立即匹配到了
_+04240002,这是打往本地网络的电话,于是剥离了目标号码的前 9 位,使之成为0001,紧接着以优先级 5 发送到ext-local本地上下文。 - [ext-local] 本地上下文精准匹配了目标
0001,将来电发送到ivr-mainIVR。
- 小猫猫的 PBX 送出的
- 呼叫被大狗狗家 PBX 配置的 IVR 接听,提示按 1,PBX 将呼叫转发给大狗狗的分机
0002。- [ivr-main] IVR 接听了来电,通过
WaitExten读取小猫猫的输入。小猫猫输入 1,此时${EXTEN}变量已变成1,精准匹配到了某条规则,拨号计划随即执行Dial命令,拨打大狗狗电话的 Endpoint。
- [ivr-main] IVR 接听了来电,通过
- 此时大狗狗不在家,系统将其拉入语音信箱进行录音。
- [ivr-main] 当呼叫未接听的时候,呼叫继续匹配到下一条规则
VoiceMail,启动语音信箱功能。完成后,通过下一条Hangup规则挂断电话。
- [ivr-main] 当呼叫未接听的时候,呼叫继续匹配到下一条规则
其它配置
通话详单记录 (cdr.conf)
为了对每一次通话进行事后审计或排查连接故障,可以启用内置的本地 CSV CDR(Call Detail Record)记录器。
以下是笔者的配置规则,它会在 /var/log/asterisk/cdr-csv/Master.csv 中生成格式化记录行,内容包括主被叫号码、通话时刻、时长(呼叫建立时长与计费时长),以及最终接听状态。
1 | |
语音信箱(voicemail.conf)
当分机不在线或无人接听时,录音系统会自动介入,并生成压缩音频文件供用户查阅。
1 | |
访问控制列表(acl.conf)
虽然外部有防火墙的保护,在应用层再加一些安全措施总是好的。同样地,这里使用的也是最长前缀匹配。
1 | |
参考配置及流程图
上文中提到的配置均节选和改编自笔者在实际运行中的 PBX 配置文件。这套配置的大部分内容均已开源,可供参考:
https://github.com/YukariChiba/asterisk-config
以上配置的基本流程图如下所示,基本囊括了上文提到的各种场景,同时还支持 PSTN 接入和 e164.dn42 兼容:
接入实体终端
能用手中的软电话进行全网呼叫固然好玩,但拥有一个 PBX,能做到的事情远不止如此。如果手中有合适的硬件,完全可以在桌面上摆一台实体电话机,并在拿起听筒时听到来自 telephony42 网络的声音。
认识接口
要将模拟设备接入 IP 网络,必须先认清 RJ11 接口背后的两种不同电气属性。虽然它们看起来长得一模一样,但插错线可能导致设备被一瞬烧毁。
- FXS (Foreign eXchange Subscriber)
这是用来连接电话机的接口。大部分 ATA 提供的都是 FXS 端口,它负责向线路馈电,提供拨号音,并在有来电时输出 90V 交流电触发电话机的机械振铃。简而言之,只要是普通的模拟电话机,就必须把它插在带有 FXS 口的设备上。 - FXO (Foreign eXchange Office)
这是用来连接电信局外线的接口,与 FXS 是一一对应关系。它不提供电压,只被动接收来自 PSTN 局端交换机的馈电和拨号音。如果想把家里传统的运营商固话(注:现在很多家庭固话是从光猫自带的 ATA 接口接出的,这属于另一种更容易接入的情景)接入 Asterisk,需要购买带有 FXO 接口的语音网关或语音卡。 - T1 / E1 / PRI
如果你碰巧从垃圾场捡来了一台带有众多 RJ45 接口的庞大语音板卡,那可能是数字中继接口。E1 在一根线上可以承载 30 路并发语音,曾是大型企业对接运营商的标准方案。但在传统运营商都在推进纯 IP 化的今天,除非有强烈的复古爱好,否则不建议折腾这些极其耗电且配置繁琐的过时硬件。
ATA 设备与 IP 话机
ATA(模拟电话适配器)能把各类模拟电话接入 SIP 网络。但需要注意的是,一些型号在选购上容易踩坑:
- Linksys PAP2T:是最经典、市面上最便宜的 ATA。但须当心市面上流通的很多是翻新主板,通话可能有较大的电流声,一些元件也可能存在暗病。
- Cisco SPA112/SPA122:是性能相对 PAP2T 而言更加优异的替代品。然而,二手海鲜市场低价买到的仍然可能是被 RingCentral 等国外运营商写死配置且锁定的定制机。这些机器无法登入管理账户从而修改 SIP 服务器等信息。如果不幸买到,必须拆机飞线接 TTL 串口进行手动重置。买前一定要询问是否无锁。
- 运营商的光猫:这是最容易取得的,仔细检查家里的光猫,如果有 RJ11 接口同时能搞到超级密码,那么它很有可能可以直接被配置接入 telephony42。但也需注意,各品牌光猫支持的 SIP 规范参差不齐,也许存在潜在的兼容性问题。

如果想体验现代办公室的感觉,也可以考虑购入带有彩色屏幕甚至摄像头的实体 IP 电话。现代的 IP 电话大多支持 G.722 宽带音频,音频质量远超模拟电话,一部分型号甚至能支持视频会议、高级来电显示等功能。但值得注意的是,部分 Cisco 的二手话机(如 7940/7960)虽然看起来很高大上,但需要 TFTP 服务器下发 XML 配置文件,甚至刷写固件后才能连接到自己的 PBX,非常折腾。
关于 ATA / IP 话机的 Dialplan 字符串
除了 Asterisk 有拨号计划,许多 ATA / IP 话机自身也有一个内部的拨号计划(用于判断用户按完几个键后应当立刻把号码送出)。如果发现在实体硬件上拨出的号码与送达 PBX 的不一致,大概率需要修改它的 Dialplan。
方便起见,我们可以让 PBX 处理这一切,这样只需要在对应位置填入:(*x.|+x.|x.) (允许一切以 * 或 + 或者数字开头的任意长度号码,立刻送出)。
关于老式电话

老式电话(例如拨盘电话)通常没有 * 和 # 键,同时需要注意 + 号的替代拨出方式,通常可用 00 替换。此外,如果购买的是拨盘电话,记得把 ATA 上的拨号延迟调整得稍大一些,除非您拨号的手速确实很快。
现在市场上有许多仿古电话,有转盘,但同时也有
*和#键甚至+键,注意分辨。
如果购买的是更加古老的、采用脉冲拨号的电话机,机械振铃通常需要更高电压的交流电来驱动。这种情况下需要购入支持脉冲拨号的 ATA,或购入脉冲转双音多频的转换器。
电气参数本地化
物理(模拟信号)线路上有国家间的电气差异。例如,如果使用中国的老座机拨号,必须在 ATA 网关的区域配置页中修改阻抗和铃音参数,否则会出现拨号识别缓慢、挂机不断线或回音严重的问题。本文不再展开,具体设置可查阅相关技术文档。
网络 QoS
在一个高负载的 dn42 节点中,BGP 表更新、大文件传输可能会瞬间占满带宽。语音对抖动和丢包率极其敏感。因此,网络层面的 QoS 干预必不可少。
在路由器和局域网交换机上,建议实施 DiffServ 标记策略:
- SIP 信令控制流 (UDP/TCP 5060):DSCP 应当被标记为 CS3 (类选择器 3,十进制 24)。
- RTP 语音数据流 (UDP 10000-20000):应当被标记为最高优先级的 EF (加急转发,十进制 46)。这会通知沿途的路由器将语音数据包放入最优先的队列中。
尽管在 dn42 网络里,没几个网络真的配置了 QoS 策略。
进阶使用
tel42verifier 与 DDDS 递归查询
当 PBX 通过 ENUMLOOKUP 函数查询 4.2.4.0.tel.dn42 时,不应当假设 DNS 能立即返回对方 PBX 的 URI 正则表达式。
一般情况而言,dn42 ENUM 记录看起来会像是这样:
1 | |
这里的 Flag 位的 u 代表终端(Terminal),意味着正则表达式替换后得到的将会是一个最终 URI(例如 sip:[email protected]),PBX 可以在拿到结果后直接向其发起呼叫。
然而,在大规模电话网的部署中,有时候只需要对 ENUM DNS 区域做重定向。此时的 NAPTR 记录会使用空 Flag "",而这正是 DDDS(动态委托发现系统, RFC 3404) 的递归查询机制的一部分。
如果查到的记录是:
1 | |
空白的 Flag 位表示这并非一个 Terminal 记录,不能直接用于呼叫,需要拿着替换后的结果去进行下一次 DNS 查询。
根据 DDDS 的机制,接下来的解析链路大致上是这样的:
- NAPTR (空),重复直至最大递归层数 / Terminal Flag 出现。
- NAPTR (U/A/S) 此时才能最终获取到终端 PBX 的访问方式:
- U 标志就是常用的 SIP URI。
- A 标志意味着返回的是一个域名,需要去查询它的 A/AAAA 记录。
- S 标志意味着返回的是一个域名,需要去查询它的 SRV 记录。
因此,DDDS 的存在使得某些特殊的情况下,直接通过 NAPTR 反向查询是不能匹配到 ENUM 呼叫来源的。(之前提到的 tel42verifier 程序实现了这一逻辑)
P-Asserted-Identity (PAI)
在理解了 CallerID 可以被随意伪造之后,接下来介绍一种通常不可伪造的身份字段(除非自己配错了):PAI(P-Asserted-Identity,RFC 3325)
在 SIP 报文的头部,通常有两个与身份相关的字段:
From:这是终端用户自己填写的 CallerID,想写谁就写谁。P-Asserted-Identity:这是由受信任的网关(例如 PBX)在鉴权后写入的受信任 ID。
例如,当好友 NyaNya 拨入你的电话时,对方的 PBX 在验证了分机密码后,会在送往你 PBX 的 SIP 报文头里插入一行:
1 | |
如果在 PJSIP 的设置里对此 Endpoint 开启了 trust_id_inbound=yes(注意,此选项应仅对已知且受信任的 peer 开启),Asterisk 在收到 SIP 报文后会读取这个 PAI 字段,最后呼叫分机时,会在来电显示中覆盖 From,转而显示这个经过上游背书的受信任号码。
在拨号计划中,也可以通过提取 PAI 字段来实施更高级的路由策略:
1 | |
STIR/SHAKEN 与 dn42
在 PSTN 世界中,为了对抗骚扰电话和欺诈,一些机构和运营商推行了 STIR/SHAKEN 协议栈(RFC 8224 / 8225 / 8226)用于对 SIP 头进行签名。
在呼叫发起端,网关使用私钥,将主叫和被叫号码、当前时间戳打包成一个 JWT(称为 PASSporT),并作为 SIP 头的 Identity 字段随 INVITE 发送。接收端网关通过查询证书链验证该签名,以此确信号码没有遭到篡改。
这是一个更加安全的方案。然而,STIR/SHAKEN 体系的证书颁发机构必须包含符合 RFC 8226 规范的专用扩展字段(TNAuthList,用于指定该证书有权代表哪些号码前缀)。遗憾的是,显然,dn42 Root CA 不可能包含这个 OID,引入新的 Root CA 又会存在信任和管理上的问题。
因此,目前在 telephony42 中,基于 DNS ENUM + AGI 的反向 IP 验证依旧是最容易实现的防伪造方案。
Fax 和 Modem 拨号
在纯 IP 网络上利用有损压缩后的音频信道进行 Modem 拨号或传真,是一种极为困难的行为艺术。
VoIP 设计之初是为了人类耳朵服务的。一般的压缩音频编码(如 Opus、G.729)在设计时为了节省带宽,采用了心理声学模型,会砍掉人耳不易察觉的高频细节,并且会随网络抖动动态调整缓冲区,导致数据传输时钟发生微小的偏移。对于依赖音频波形相位来解调的 V.90/V.34 Modem 信号而言,哪怕是毫秒级的时钟偏移,在信道层面上也能导致连接中断。
如果坚持要在 dn42 中听到猫叫,有以下几个建议:
- 必须强制使用 alaw 或 ulaw 等无压缩编码。
- 禁用目标分机的 VAD(静音检测)和 Jitter Buffer,保证音频流每一毫秒都严格对应上时钟。
- 如果是接传真机,最完美的方案是开启 T.38 支持(但这样也会降低许多折腾的乐趣)。
- 如果没有硬件传真机,希望使用纯软件收发传真,可以通过
iaxmodem创建虚拟 Modem tty,配合hylafax或efax等传真软件。iaxmodem使用 IAX2 协议直连 Asterisk 内部信道,绕过了外部 SIP 栈的诸多潜在问题。 - 如需 SIP 软件 Modem 实现拨号,在此推荐使用 jerryxiao 的
d-modemfork。
对接 PSTN
如果希望让 telephony42 的分机能够打通 PSTN 世界的手机和座机,就需要找一家商业的 VoIP Trunk 提供商。只需在提供商侧进行简单配置,就可以将 PSTN 世界的号码(如北美 +1,中国大陆 +86)映射进自己的 Dialplan 了。
与提供商对接通常有两种模式:
- SIP Registration:最常见的方案。PBX 表现得就像一个普通的客户端,向提供商的服务器注册并保持心跳连接。这需要在
pjsip.conf里增加一个type=registration的区块。 - SIP URI:适合有静态 IP 的 PBX。只需告诉提供商 PBX 的外部可达 IP,提供商就会把 PSTN 的来电,直接以裸 SIP INVITE 报文的形式发往 SIP URI 对应的 PBX。只需添加对应 IP 的 Endpoint,然后在防火墙里将提供商的信令 IP 段放行即可,无需 PBX 主动发起注册。
对接 PSTN 后,拨号计划将直接与金钱挂钩。因此在处理相关路由时,最重要的一点是对目的地进行精细的费率切分。
在下面的拨号计划示例中,我们首先将号码规整化,然后将去往北美(+1)的路由进行拦截。如果是 800/888 等免费(Toll-free)电话号码,我们直接放行;如果是普通的需要付费的号码,系统会先切入一个自定义的警告提示音,告知用户即将发起计费呼叫,等待用户响应后再将信令发送给上游,从而避免误拨带来的损失。
1 | |
对接 WebRTC
随着 WebRTC 技术的成熟,现在我们甚至不需要任何客户端,直接在浏览器里就可以打电话了!
利用 sip.js 库,写几十行 JavaScript 就能在网站里挂载一个网页客户端。访客只需要点击一下,就能够通过 WebSocket 隧道呼叫 telephony42 网络内任意一台电话机!
对于 Asterisk 而言,配置 WebRTC 需要先在 http.conf 和 pjsip.conf 开启 HTTP/WSS 服务:
1 | |
1 | |
注意:现代浏览器强制要求 WebRTC 必须运行在 HTTPS 和 WSS 环境下。因此 Web 页面和 WSS 均需要配置可信的证书,否则浏览器将直接拒绝相关权限。
随后,在 pjsip.conf 增加 WebRTC 模板:
1 | |

phone42 正是利用了此种技术实现的,可实现在网页中拨打 telephony42 中的任意电话。
此外需要注意,开放的 WebRTC 端口依然有恶意扫描和骚扰电话的风险,同样需要开启防火墙和白名单。另外,对于任何人都可以使用的、公开的 WebRTC 服务,建议在 CallerID 中带上来源的 IP 地址以便审计。
用 SIP MESSAGE 发短信
除了建立音频/视频的 RTP 传输环境外,SIP 协议本身提供了一种名为 MESSAGE 的方法,可以在不触发媒体流协商的情况下,直接通过 SIP 端口投递纯文本数据。目前,大多数软电话系统都原生支持这一特性。
前面的 ext-local 拨号计划中,也可以用这个逻辑拦截 SIP MESSAGE 然后将其转发到分机:
1 | |
此时如果配置了 CDR,可以看到有一条 CallerID 为空的呼叫被发往了 Message/ast_msg_queue Endpoint。因为 SIP Message 并不是常规的通话,Asterisk 不会为其创建标准 SIP 通道,所有 out-of-dialog 的 SIP Message 都会使用这个特殊的伪通道来解码和传递数据。
在非最新最热版本的 Asterisk 上转发 SIP Message 可能会出现
not a valid SIP/SIPS URI报错,这是已知问题,请手动调整 URI 或等待版本更新。
结语
由于传统电信巨头的高度垄断,SIP 和 RTP 协议往往被锁死在运营商封闭的核心网里,普通人很难接触到它的全貌。
然而,在 dn42 这个平行宇宙里,我们能够利用自治网络、BGP 路由、开源软交换技术以及自己天马行空的想象力,亲手搓出一个横跨大洲的电话系统,这本身就是一件极具浪漫色彩的事。
我们在哪里传递 SIP 报文,哪里就是我们自己的电话网络。
