Asterisk 源码编译到 PJSIP 分机注册:依赖、menuselect 与 RTP 排障
项目地址:https://github.com/asterisk/asterisk
官网:https://www.asterisk.org
Asterisk 是开源 PBX 电话系统的代表方案。
为什么不直接装发行版仓库的 asterisk 包
仓库包普遍落后主线好几个大版本,部分模块被裁剪,文件布局和官方 systemd 单元不一致。更麻烦的是仓库里的 PJSIP 栈往往单独打包,版本与主程序对不上时会出现「模块加载失败但服务进程正常」的状态,日志要到很靠后位置才看到 ERROR。
长期维护建议直接下官方 LTS 源码包编译:缺哪个 -devel 包,configure 阶段就会报出来,不会把问题拖到运行期。
安装依赖
RHEL/CentOS 系:
sudo yum install -y epel-release
sudo groupinstall -y "Development Tools"
sudo yum install -y wget libxml2-devel libsqlite3-devel ncurses-devel \
uuid-devel openssl-devel libedit-devel libjansson-devel
Debian/Ubuntu 系换成 -dev 包名:
sudo apt update
sudo apt install -y build-essential wget libxml2-dev libsqlite3-dev \
libncurses-dev uuid-dev libssl-dev libedit-dev libjansson-dev
几个库各自的作用:libxml2 负责解析部分模块的 XML 配置,缺了编译能过但部分 app 模块用不了;sqlite3 是 CDR 通话记录和内置数据库后端;jansson 是 JSON 解析库,res_pjsip 的配置和消息处理直接依赖它,很多「装完注册不上」是这里缺头文件导致 PJSIP 被悄悄跳过;libedit 给 asterisk -r 控制台提供历史命令。
configure 参数
tar xf asterisk-*.tar.gz
cd asterisk-*/
./configure --with-jansson-bundled --with-pjproject-bundled
configure 会打印 summary,重点看最下方 jansson、libedit、sqlite3 是 FOUND 还是 NOT FOUND。出现 NOT FOUND 就回去补依赖,不要用 --without-* 强行绕过。
--with-jansson-bundled 和 --with-pjproject-bundled 的意思是:把 jansson 和 PJProject 源码一并编译,而不是去找系统版本。PJSIP 栈对这两个库的符号版本很敏感,系统库太新或太旧都会导致运行时异常,用 bundled 版本可以锁死行为。
menuselect 选模块
make menuselect 打开 ncurses 界面,生成 menuselect.makeopts。四处要看:
- Channel Drivers:确认 chan_pjsip 被选中,旧的 chan_sip 不要两个都开
- Resource Modules:保留 res_pjsip、res_rtp_asterisk
- Applications:按需勾选 app_dial、app_queue、app_voicemail、app_playback,接队列客服别漏 app_queue
- Formatters:至少保留 format_wav 和 format_alaw
纯命令行环境可以用非交互方式:
make menuselect.makeopts
make menuselect --disable channel_chan_sip --enable app_queue
编译安装
make -j$(nproc)
sudo make install
sudo make samples
sudo make config
sudo ldconfig
4 核机器一般 15 到 40 分钟。make -j$(nproc) 报内存不足就降到 -j2。
make config 生成 systemd service 文件:
sudo systemctl enable --now asterisk
sudo systemctl status asterisk
asterisk -V
make samples 生成带注释的示例配置,但 sip.conf、extensions.conf 里是示例值,生产环境应逐项覆盖,不要直接复用。目录权限是 root,若后面用 asterisk -rx 改配置,建议统一 chown -R asterisk:asterisk /etc/asterisk。
PJSIP 分机注册:endpoint / auth / aor
chan_sip 已进入维护停摆,现代 LTS 主线的 SIP 栈是 res_pjsip,配置在 /etc/asterisk/pjsip.conf,控制台命令是 pjsip show ...。
PJSIP 对象必须分离:endpoint 描述「这个人是谁」,auth 描述「他拿什么密码验证」,aor 描述「他注册到哪、允许几个注册」。
让软电话注册成 1001 的最小 pjsip.conf:
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
[1001]
type=endpoint
context=internal
disallow=all
allow=ulaw,alaw
auth=1001-auth
aors=1001-aor
[1001-auth]
type=auth
auth_type=userpass
username=1001
password=Zzz2024
[1001-aor]
type=aor
max_contacts=1
qualify_frequency=30
disallow=all 后再 allow=ulaw,alaw 是必写组合,先清空全部编解码再按优先级打开,否则双方会协商出系统默认的乱序编码。qualify_frequency=30 表示每 30 秒发一次 OPTIONS 探测在线状态,pjsip show contacts 里能看到 Avail / Unavail。
注册流程:话机发 REGISTER,Asterisk 回 401 Unauthorized 带 nonce,话机用密码和 nonce 计算 digest 后重发,通过后返回 200 OK,contact 注册到 AOR。所以别只看「注册失败」,先看是 401 之后没有第二次请求(认证配置没生效),还是第二次请求被 403 拒绝(密码不对或账号不存在)。
常见误用是把 context 写到 auth 里。auth 只负责认证,呼叫走向由 endpoint 上的 context 决定。另一个错误是把 type=endpoint 和 type=auth 写进同一个 [1001] 段,配置解析会直接失败——同名段会合并,但不同 type 之间必须是并列关系。max_contacts=1 只允许一台设备注册,软电话与实体话机共用同一分机时应设为 2 并配合 remove_existing。
extensions.conf 拨号计划
[internal]
exten => _1XXX,1,NoOp(内部呼叫 ${EXTEN})
same => n,Dial(PJSIP/${EXTEN},30)
same => n,Playback(ss-noservice)
same => n,Hangup()
_1XXX 是模式匹配,1XXX 匹配 1001 到 1999。Dial 的 30 是 30 秒无应答则继续下一步。same => n 延续上一优先级,比手写递增数字好维护。
用 DIALSTATUS 分支:
[internal]
exten => _1XXX,1,Dial(PJSIP/${EXTEN},30)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?ok:fail)
same => n(fail),Playback(ss-noanswer)
same => n,Hangup()
same => n(ok),Hangup()
DIALSTATUS 常见值有 ANSWER、BUSY、NOANSWER、CANCEL。占线可播 busy 提示,超时可转语音信箱。
队列场景:
; queues.conf
[marketing]
musicclass=default
strategy=ringall
timeout=20
member => PJSIP/1001
member => PJSIP/1002
; extensions.conf
[callcenter]
exten => s,1,Queue(marketing)
same => n,Hangup()
strategy=ringall 所有客服同时振铃,leastrecent 优先呼叫最近最少接听的坐席。wrapuptime、maxlen 在压测前建议调短值观察。修改 queues.conf 不需重启,module reload app_queue.so 即可,queue show marketing 看成员实时状态。
重载顺序建议先拨号计划后模块:
sudo asterisk -rx "dialplan reload"
sudo asterisk -rx "module reload res_pjsip.so"
sudo asterisk -rx "module reload app_queue.so"
验证与 RTP 排障
sudo asterisk -rvvvv 进控制台后:
pjsip show endpoints
pjsip show contacts
core show uptime
pjsip show contacts 的 Contact 一列出现 sip:1001@192.168.x.x:xxxxx 就说明注册链路通了。日常用 -r 即可,别长期开高 verbosity 刷屏。
注册正常但一接通就断,常见两类原因:
编解码协商不一致。rtp set debug on 后拨测试电话看 RTP 包是否双向到达,看不到对端 RTP 一般和防火墙有关。Asterisk RTP 默认端口段在 /etc/asterisk/rtp.conf 的 rtpstart 和 rtpend,通常 10000–20000 UDP,需在防火墙放行。
第二类是 SIP 会话计时器。部分软电话默认开启 session-timers,Asterisk 若几个周期内没收到刷新请求会主动拆线,可临时置 session-timers=accept 观察。
总结
编译依赖、模块选择、PJSIP 注册、拨号计划路由、RTP 排障——把 pjsip show contacts 和 rtp set debug on 两个命令记熟,生产环境一半以上通信问题靠它们定位。