Eddie(艾迪)的博客

7年VoIP工作经验,前端/软交换开发。热爱开源,热爱分享。 微信:suguswang177, 欢迎交流

【案例分享】JSSIP 电话无法挂断问题

当听到分机无法挂断电话时,通常有以下几种可能的原因: 在做Record-Route时,使用了错误的内外网IP地址。导致BYE请求按照route头发送时,无法正确找到对应的服务器。 Contact头部的URI不正确,导致BYE请求无法找到对应的服务器。 时序图如下; sequenceDiagram autonumber participant u1 as user1 participant o as opensips participant u2 as user2 u1->>o: INVITE o->>u2: INVITE u2-->>o: 200 OK o-->>u1: 200 OK u1->>o: ACK o->>u2: ACK u2->>o: BYE o-->>u2: 477 Send failed (477/TM) 477错误一般是按照route头或者contact转发时,找不到对应的socket。 在使用tcp作为传输协议时,例如tcp/tls/wss注册的分机比较常见。 有以下可能 分机到opensips的tcp连接断开 contact使用错误的transport参数 从过观察第2个信令的Conact头,发现transport=ws, User-Agent=JSSip。 正常情况下,jssip应该使用wss作为transport。 所以解决办法是,在jssip的配置中,将transport改为wss。 还有一个解决方案, 就是让jssip通过nginx转发wss请求,让nginx转发到opensips的ws端口, 也能解决问题。 sequenceDiagram autonumber jssip ->> nginx: wss nginx ->> opensips: ws

2025-07-15 20:00:08 · 1 min · Eddie Wang

【案例分享】外线呼入,SIP分机为何无响应?

案例分享 最近有客户反馈,自己的话机最近一两周都没有收到来电了,感觉很奇怪,他自己呼叫400号码,然后转分机,也接不通。 组网结构 flowchart LR ua1(分机) fw1(防火墙) uas1(SIP服务器) ua1 --> fw1 fw1 --> uas1 排查思路 首先排查服务端,从日志来看,分机的注册是正常的,每隔30多秒就注册一次。 然后排查呼叫信令图,发现发送给分机的INVITE, 分机那边没有任何反应 接着请求客户远程协助,在分机上抓包,发现只能抓到注册包,没有INVITE的回应。 询问客户,他们公司有没有使用防火墙,客户说有。 然后让客户检查他们防火墙的设置,关闭SIP ALG功能,但是也无效 然后让客户找网络负责人,在防火墙上抓包,发现防火墙收到了INVITE,但是没有转发给内部分机, 原因未知 最后找防火墙厂商,发现是防火墙的UDP组包没有开启,开启UDP分片重组后,呼入电话能正常进线 总结 此刻,我回想起曾经写的 UDP分片导致SIP消息丢失 没想到,在防火墙上也遇到了同样的问题。

2025-07-15 19:39:57 · 1 min · Eddie Wang

RTPEngine 录制 PCAP 文件

为什要用 RTPEngine 来录制 PCAP 文件? 一般我们用 Freeswitch 来作为录音服务器, 但是某些场景,例如备份录音,需要在不同节点进行录音。 如果直接录制成 wav 文件,那么比较占用资源,而且备份录音用的几率也是比较小的。 因此录制成 PCAP 文件,可以节省资源,后期 pcap 转语音也能比较容易的实现。 实现步骤 配置rtpengine启动参数 --pcaps-dir=/var/log/records --record-method=pcap --recording-format=eht 在opensips在做SDP Offer rtpengine_offer("record-call=yes"); 录音文件位置 录音文件在/var/log/records目录下,文件名是呼叫的sip Call-ID-16hex随机数.pcap call1-1234567890abcdef.pcap call2-1234567890abcdef.pcap 录音文件内容 录音文件用wireshark分析,可以听到主被叫双方的声音。 其他 除了录音文件,一些录音的元数据,例如SDP之类的信息,会被记录到录音的目录下。

2025-07-15 19:26:06 · 1 min · Eddie Wang

SIP安全 - DTLS client Hello 攻击白皮书

TL;DR 攻击者伪造DTLS ClientHello消息,在SIP服务器和客户端之间建立一个非预期的连接。导致正常链接被阻断。 影响软件 FreeSWITCH RTPengine asterisk FreePBX 漏洞白皮书 webrtc-hello-race-conditions-paper 表现 应答后呼叫无声 参考 https://github.com/EnableSecurity/advisories/tree/master/ES2023-03-rtpengine-dtls-hello-race https://github.com/EnableSecurity/advisories/tree/master/ES2023-02-freeswitch-dtls-hello-race https://github.com/EnableSecurity/advisories/tree/master/ES2023-03-rtpengine-dtls-hello-race

2025-07-14 23:13:23 · 1 min · Eddie Wang

OpenSIPS Summit 2025 速看

2025 年 5 月 27 - 5 月 30 日, OpenSIPS Summit 2025 在荷兰阿姆斯特丹举行。 最近我才有时间,看完所有的会议资料,包括 PDF 和 PPT。 下面是我整理的,认为比较有价值的一些内容。以飨读者。 1. 加强 SDP 处理 对 SDP 的处理,如果用 OpenSIPS 脚本来做,将会非常蹩脚。 生产环境一般都是使用 rtpengine 或者 rtpproxy 来处理。 但是,最近的 OpenSIPS 版本,已经可以支持 SDP 处理了。 可以直接在 OpenSIPS 脚本里处理 SDP。 说实话,我看了新的方案,我觉得,还不如用 rtpengine 或者 rtpproxy。 但是聊胜于无吧,感兴趣的可以看看原文。 OpenSIPS Summit 2025 - Liviu Chircu - Enhanced Media Operations with Structured SDP 除此以外,PDF 也提到一些有趣的事情,比如 SDP 随着时间推移,增强和很多功能,包也变得越来越大 时期 内容 包大小 1998 基本媒体行 200-400 bytes 2002 编码协商,rtpmap 500-1000 bytes 2010 ICE/DTLS 1-2 kb 2015 WebRTC, Simulcast, Bound, MID 2-3kb 在线会议,SFU 3-5 kb 可以想象,随着媒体能力的增强,UDP包的SIP信令中的分片几乎成为必然,所以,是否可以考虑有限使用TCP/TLS来传输信令呢? ...

2025-07-14 21:52:23 · 1 min · Eddie Wang

RTPEngine STUN包处理流程

STUN 请求处理 flowchart TD __wildcard_endpoint_map-->__assign_stream_fds monologue_offer_answer-->__assign_stream_fds monologue_publish-->__assign_stream_fds monologue_subscribe_request1-->__assign_stream_fds call_make_transform_media-->__assign_stream_fds __wildcard_endpoint_map -->__get_endpoint_map monologue_offer_answer -->__get_endpoint_map monologue_publish -->__get_endpoint_map monologue_subscribe_request1 -->__get_endpoint_map call_make_transform_media -->__get_endpoint_map __assign_stream_fds --> stream_fd_new __get_endpoint_map --> stream_fd_new stream_fd_new --> stream_fd_recv stream_fd_new-->stream_fd_readable stream_fd_readable-->__stream_fd_readable stream_fd_recv--> __stream_fd_readable--> stream_packet--> media_demux_protocols --> stun --> __stun_request --> ice_request 从SDP Offer之后,stream_fd_new 函数里做了几个事件订阅, 当对应的的媒体端口收到包之后,这个包可能是好几种协议,例如RTP, DTLS, STUN等。 在media_demux_protocols() 中决定了这个包是以上包的哪一种, 如果是STUN包,则进入stun()中处理。 STUN包也分为请求和响应,当消息是响应时,进入ice_request(). 1int ice_request(stream_fd *sfd, const endpoint_t *src, 2 struct stun_attrs *attrs) 3{ 4 struct packet_stream *ps = sfd->stream; 5 struct call_media *media = ps->media; 6 struct ice_agent *ag; 7 const char *err; 8 struct ice_candidate *cand; 9 struct ice_candidate_pair *pair; 10 int ret; 11 12 ilogs(ice, LOG_DEBUG, "Received ICE/STUN request from %s on %s", 13 endpoint_print_buf(src), 14 endpoint_print_buf(&sfd->socket.local)); flowchart TD ice_update-->__do_ice_checks ice_agents_timer_run-->__do_ice_checks __do_ice_checks --> __do_ice_check

2025-07-03 22:26:35 · 1 min · Eddie Wang

DTLS协商失败导致无声问题

1. 简介 DTLS(Datagram Transport Layer Security,数据报传输层安全协议)是一种为基于数据报的应用(如UDP)提供安全通信的协议。它是TLS(传输层安全协议)的扩展,专门设计用于不可靠的传输协议(如UDP),以实现数据加密、身份认证和消息完整性保护。DTLS常用于VoIP、视频会议、WebRTC等实时通信场景。 DTLS在WebRTC音视频中是强制必须使用的,否则媒体协商阶段就会失败。 使用DTLS后,就算中间人从网络中抓包,抓到了RTP流,在播放RTP流时,里面也全是噪声,无法播放的。 今天要讲的是就是DTLS协商失败导致电话即使接通,也无声的问题。 2. 网络结构说明 UAS: sip server, OpenSIPS + RTPEngine组成 UAC: WebRTC 分机 F1. UAS ----> UAC: INVITE (SDP) F2. UAS <---- UAC: 180 F3. UAS <---- UAC: 200 Ok (SDP) F4. UAS ----> UAC: ACK 从SIP信令上看,被叫应答后,UAS和UAC之间的双向媒体流应该建立起来,但是实际上却无声。 3. ICE/STUN/DTLS ICE: WebRTC中的ICE(Interactive Connectivity Establishment,交互式连接建立)是一种网络协议,用于帮助WebRTC客户端在不同网络环境下建立点对点(P2P)连接。ICE的主要作用是解决NAT穿透和防火墙问题,使两个终端能够找到最佳的通信路径。 STUN: WebRTC中的STUN(Session Traversal Utilities for NAT,NAT会话穿越实用工具)是一种网络协议,主要用于帮助客户端发现自己的公网IP地址和端口。由于很多设备处于NAT(网络地址转换)或防火墙之后,直接通信会遇到障碍,STUN可以让客户端知道外部网络如何访问自己 在分机应答后,并在WebRTC发送本地媒体流前,还需要两个步骤协商完成UAC才会发送语音流 3.1 STUN协商 UAC收到来自UAS的SDP, 里面一般有如下内容 m=audio 54322 UDP/TLS/RTP/SAVPF 111 0 8 a=ice-ufrag:abcd a=ice-pwd:1234567890abcdef a=ice-options:trickle a=fingerprint:sha-256 12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF a=setup:actpass a=candidate:1 1 udp 2130706431 1.2.3.4 54322 typ host STUN是一个请求响应模型的协议。UAC将会向a=candidate里的1.2.3.4:54322 发送UDP消息 ...

2025-06-24 21:06:11 · 2 min · Eddie Wang

RTPEngine mr13版本, 特殊的fmtp参数会导致某些语音编码被移除

场景说明 如下图所示 Offer阶段,F1, F2 步骤里PCMU编码有个fmtp参数abc=no, 这个参数可能只对呼叫发起方有意义,对被叫方来说,只会被忽略。 Answer阶段, 例如被叫是个FreeSWITCH, 它不认识abc=no, 就直接忽略,然后应答编码是PCMU。但是rtpengine认为不带abc=no的参数,就认为这个PCMU的编码是不可能被选中的,然后就直接删掉了PCMU编码, 只保留了一个101编码 由于主叫收到101的编码,而没有语音的编码,所以主叫收到200 Ok后立马就发送了BYE // F1: send offer to rtpegnine m=audio 2000 RTP/AVP 0 8 101 a=rtpmap:0 PCMU/8000 a=fmtp:0 abc=no a=rtpmap:8 PCMA/8000 a=fmtp:8 abc=no a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 // F2: receive offer from rtpengine m=audio PORT RTP/AVP 0 8 101 c=IN IP4 203.0.113.1 a=rtpmap:0 PCMU/8000 a=fmtp:0 abc=no a=rtpmap:8 PCMA/8000 a=fmtp:8 abc=no a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 // F3: send answer to rtepngine m=audio 2002 RTP/AVP 0 101 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 // F4: expect receive answer from rtpengine m=audio PORT RTP/AVP 0 101 c=IN IP4 203.0.113.1 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-15 问题原因 刚开始我以为是, https://github.com/sipwise/rtpengine/commit/9c00f30 这次commit引起的问题,我尝试注释代码,然后进行测试,发现可以解决问题。 ...

2025-06-18 20:04:52 · 1 min · Eddie Wang

VoIP Server 高可用架构设计

简介 VoIP高可用包含很多方面,主要包括: 接入层高可用:怎么保证某个接入点出现问题,不会影响到整个平台不可用? 核心层高可用:怎么保证路由选择的高可用,某个网关不可用,继续尝试其他网关? 存储层高可用:怎么保证不会丢失录音文件,怎么保证录音文件的完整性? 数据库层高可用:怎么保证注册信息能够在集群中同步? 接入层高可用 最简单的场景, 只有一个fs, 无论是ua还是gw都接入到fs1上 ua1 ----> fs1 ua2 ----> fs1 gw1 ----> fs1 gw2 ----> fs1 如果要实现高可用,最简单的做法就是再加一个fs2。但是加了fs2之后,ua和gw的接入策略就需要改变了。 为了保持客户端的接入策略不变,最简单的做法加上一个负载均衡器,然后由负载均衡器来做信令转发。 ua1 ----> op1 ----> fs1 ua2 ----> | ----> fs2 gw1 ----> | gw2 ----> | op1是单节点的,也会存在单点故障问题,因此引入op2。 ua1 ----> op1 ----> fs1 ua2 ----> op2 ----> fs2 gw1 ----> | gw2 ----> | 引入op2之后,有两个方案来保证高可用: 方案1, 主备: op1和op2用VIP来对外提供服务,op1和op2的VIP是一样的。实际只有一个在工作,另外一个处于备份状态。 一旦op1出现问题,op2接管VIP对外提供服务。 - 优点: 简单 - 缺点: - 兼容性:不是所有云平台都支持VIP - 成本:需要额外的硬件成本,而且还是闲置状态 - 负载:op1和op2都需要对外提供服务,但是实际上只有一个在工作,一个节点的处理能力有限,总会达到瓶颈 ...

2025-05-27 20:10:22 · 1 min · Eddie Wang

离谱的通话无声问题排查记录

简化网络模型 A 硬件话机 S SIP 服务器/媒体服务器 B 客户手机 A <-SIP/RTP> S <-SIP/RTP> B 问题说明 通话接通后,双方听不到对方的声音。 呼叫流程 S 发起呼叫,A 接听后,bridge B 排查思路 检查S在INVITE A时,SDP里携带的c=行,是否是S的公网IP。检查后无问题 在A上抓包,发现有 S->A的RTP包,也有 A->S 的 RTP 包。 将第二步的抓包文件拿到wireshark中分析。发现S->A方向的包是正常的,能播放出声音;A->S方向虽然有RTP包,但是看波形是一条直线,非常奇怪。 S->A方向的RTP包,在wireshark中能播放出声音,理论上A应该能听到B的声音的。但是用户反馈听不到。 咨询用户,用户是如何接听电话的。用户反馈是通过话机手柄接听的电话 让后让客户使用免提的方式拨打接听电话,发现可以听到声音。 让客户拍照话机手柄插入话机的接口,发现手柄插入的接头插入了耳麦的接口。 让客户将手柄插入正确的手柄接口,问题解决。 延伸思考 这次问题排查,让我想起了之前看过的思科的排除语音质量问题 这篇文章把语音质量问题分为三类: 网络相关(包括网关(GW)和PSTN问题) 电话型号/固件相关 设备(例如头戴式耳机)相关 在步骤上: 步骤1.隔离语音质量问题的第一步是找出确切的用户体验并与他们交谈(无论是面对面还是通过电话),以获得对语音质量问题的准确描述。如果有大量用户报告问题,请与他们的样本(可能是5-10个)交谈,以获得对症状的准确描述。 但是实际情况下,我们有时候迫于形势,往往只从一两个客户那里获得只言片语的信息,然后就开始排查了。客户的只言片语如果带有误导性,那么排查的方向就错了。 步骤2.记录物理位置(例如站点A,2楼)、用户名(用户的电话)、目录号码(DN)、电话型号(例如8865)、电话固件(例如11.5.1)和遇到语音质量问题的电话的IP地址。创建一个按物理位置排序的电子表格。在开始排除故障时创建此电子表格需要30分钟(或更短时间),可节省数小时甚至数天的故障排除时间。 记录信息很重要,但是实际操作中,往往因为时间紧迫,而没有记录这些信息。或者这些信息记录在头脑中,但是排查到最后才发现,自己忘记了某些信息。 步骤3.创建电子表格后,查看电话列表,了解电话有哪些共同之处,以及它们与没有语音质量问题的其他电话有何不同。之后,您可以意识到所有有问题的电话都位于同一栋大楼和同一楼层,您可以意识到有问题的电话连接到最近升级的交换机,或者您可以看到所有有问题的电话都位于特定固件上。 总结 搞VoIP这么久,碰到过很多疑难杂症。排查思路往往都是从软件或者网络策略层面去排查。 很少考虑到终端的固件和硬件设备上。 但是这次排查通话无声的问题,让我印象深刻的是,排查到最后才发现是硬件问题。 从声音的输出输出上,有不同的设备,例如有线耳机、蓝牙耳机、话机手柄、免提等。 排查这类问题,需要多方面考量。需要挖掘客户的实际使用场景,才能定位到真正的问题。 反思 客户的硬件话机,有两个接口。 手柄的接口和耳麦的接口是共用一套接口的。虽然接口上标注的很清楚,但是客户在接线的时候,没有注意到这点。 这要怎么避免呢? 骂客户是傻逼吗? 是不是在设计接口的时候,就应该使用不同形状的接口。 比如手柄接口是圆形,耳麦接口是方形。 这样就能避免这种问题了。 或者说,接口是通用的。无论是手柄还是耳麦,插上都能用呢。 还有一种方案,就是只保留一个接口,手柄和耳麦共用一套接口。两者是互斥关系。

2025-05-07 22:45:26 · 1 min · Eddie Wang