【案例分享】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
【案例分享】外线呼入,SIP分机为何无响应?
案例分享 最近有客户反馈,自己的话机最近一两周都没有收到来电了,感觉很奇怪,他自己呼叫400号码,然后转分机,也接不通。 组网结构 flowchart LR ua1(分机) fw1(防火墙) uas1(SIP服务器) ua1 --> fw1 fw1 --> uas1 排查思路 首先排查服务端,从日志来看,分机的注册是正常的,每隔30多秒就注册一次。 然后排查呼叫信令图,发现发送给分机的INVITE, 分机那边没有任何反应 接着请求客户远程协助,在分机上抓包,发现只能抓到注册包,没有INVITE的回应。 询问客户,他们公司有没有使用防火墙,客户说有。 然后让客户检查他们防火墙的设置,关闭SIP ALG功能,但是也无效 然后让客户找网络负责人,在防火墙上抓包,发现防火墙收到了INVITE,但是没有转发给内部分机, 原因未知 最后找防火墙厂商,发现是防火墙的UDP组包没有开启,开启UDP分片重组后,呼入电话能正常进线 总结 此刻,我回想起曾经写的 UDP分片导致SIP消息丢失 没想到,在防火墙上也遇到了同样的问题。
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之类的信息,会被记录到录音的目录下。
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
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来传输信令呢? ...
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
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消息 ...
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引起的问题,我尝试注释代码,然后进行测试,发现可以解决问题。 ...
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都需要对外提供服务,但是实际上只有一个在工作,一个节点的处理能力有限,总会达到瓶颈 ...
离谱的通话无声问题排查记录
简化网络模型 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这么久,碰到过很多疑难杂症。排查思路往往都是从软件或者网络策略层面去排查。 很少考虑到终端的固件和硬件设备上。 但是这次排查通话无声的问题,让我印象深刻的是,排查到最后才发现是硬件问题。 从声音的输出输出上,有不同的设备,例如有线耳机、蓝牙耳机、话机手柄、免提等。 排查这类问题,需要多方面考量。需要挖掘客户的实际使用场景,才能定位到真正的问题。 反思 客户的硬件话机,有两个接口。 手柄的接口和耳麦的接口是共用一套接口的。虽然接口上标注的很清楚,但是客户在接线的时候,没有注意到这点。 这要怎么避免呢? 骂客户是傻逼吗? 是不是在设计接口的时候,就应该使用不同形状的接口。 比如手柄接口是圆形,耳麦接口是方形。 这样就能避免这种问题了。 或者说,接口是通用的。无论是手柄还是耳麦,插上都能用呢。 还有一种方案,就是只保留一个接口,手柄和耳麦共用一套接口。两者是互斥关系。