nom 学习笔记 Ch1
环境说明 rust版本 1.87.0 cargo版本 1.87.0 nom版本 8.0.0 解析逻辑 输入一个字符串 创建一个解析器 解析器调用parse方法 如果解析成功,返回结果,结果包含两部分内容,前者是没有匹配的内容,后者是匹配到的内容 举例 例1 空解析 这个例子描述了如何使用nom解析一个字符串。 输入是候输入字符串为my_input,匹配到的内容是空字符串,没有匹配到的是my_input。 use nom::IResult; use std::error::Error; pub fn do_nothing_parser(input: &str) -> IResult<&str, &str> { Ok((input, "")) } fn main() -> Result<(), Box<dyn Error>> { let (remaining_input, output) = do_nothing_parser("my_input")?; assert_eq!(remaining_input, "my_input"); assert_eq!(output, ""); Ok(()) } 例2 字符串匹配 pub use nom::bytes::complete::tag; pub use nom::IResult; use std::error::Error; fn parse_input(input: &str) -> IResult<&str, &str> { // tag 函数的返回结果也是一个函数 tag("Call-ID:")(input) } fn main() -> Result<(), Box<dyn Error>> { let (leftover_input, output) = parse_input("Call-ID: helloWorld")?; assert_eq!(leftover_input, " helloWorld"); // tag 函数匹配成功后返回剩余的输入 assert_eq!(output, "Call-ID:"); // tag 函数匹配成功返回匹配的内容 assert!(parse_input("defWorld").is_err()); Ok(()) } 例3 预定义匹配类型 alpha0: 识别0个或者多个大小写字符: /[a-zA-Z]/. alpha1: 和前者相同,并且至少返回一个字符 alphanumeric0: 识别0个或者多个大小写字符和数字: /[0-9a-zA-Z]/. alphanumeric1 和前者相同,并且至少返回一个字符 digit0:识别0个或者多个数字: /[0-9]/. digit1 和前者相同,并且至少返回一数字 multispace0: 识别0个或者多个空格, 制表, 会车和换行符 multispace1 和前者相同,并且至少返回一数字 space0: 识别0个或者多个空格和制表符. space1 和前者相同,并且至少返回一数字 line_ending: 识别行尾 (\n and \r\n) newline: 匹配换行符 \n tab: 匹配制表符 \t use nom::character::complete::alpha0; pub use nom::IResult; use std::error::Error; fn parse_input(input: &str) -> IResult<&str, &str> { // tag 函数的返回结果也是一个函数 alpha0(input) } fn main() -> Result<(), Box<dyn Error>> { let (leftover_input, output) = parse_input("Call-ID: helloWorld")?; assert_eq!(leftover_input, "-ID: helloWorld"); // tag 函数匹配成功后返回剩余的输入 assert_eq!(output, "Call"); // tag 函数匹配成功返回匹配的内容 assert!(parse_input("defWorld").is_ok()); Ok(()) } 参考 https://tfpk.github.io/nominomicon/chapter_2.html
SipWise C5 架构分析 WIP
SIPWISE C5 架构分析 Sipwise C5(又称NGCP——下一代通信平台)是一款基于SIP的开源Class 5 VoIP软交换平台 整体架构上,sipwise C5 是一个三明治架构, 除了sipwise, 我也看到过类似的VoIP架构, 可见英雄所见略同。 接入层 接入层是整个平台的信令出入口,主要的职责是安全检测和负载均衡。 并不具有业务上的功能,另外也不会使用数据库。 职责 外部SIP信令的入口和内部信令的出口 SIP信令完整性检查 拒绝环回SIP信令 DOS攻击检测 暴力攻击检测 TLS转UDP转换 NAT穿透映射 端口 5060, SIP/TCP+UDP, 对外接收SIP消息 5061, SIP/TLS, 对外接收TLS消息 5060, XMLRPC/TCP, 对内处理RPC调用,来控制kamailio 这里有个风险,5060/TCP端口同时用来对外处理SIP消息和对接处理RPC调用,难道 实现 参考 https://www.sipwise.com/doc/mr6.5.8/sppro/ar01s02.html https://www.sipwise.com/doc/mr10.3.1/spce/ce/mr10.3.1/architecture/architecture.html
使用m4给opensips脚本增加预处理能力
demo 代码仓库 : https://cnb.cool/eddie2072/opensips-forum/-/tree/main/how-to-use-m4 使用m4给opensips脚本增加预处理能力 为什么要预处理? 运维便利。有预处理,脚本里的IP地址,端口,密码,用户名等信息,可以由运维人员统一配置,脚本只需要引用配置文件,就可以完成脚本的运行。否则,运维人员只能手工去修改每个配置写死的地方。容易出错,且非常麻烦。 所以,我们在写脚本的时候,需要从脚本中抽离配置性质的数据。例如监听的IP地址,端口,数据库的用户名和密码等信息。 这样脚本就变层两个部份,配置文件 + 脚本本身。 运维人员只需要修改配置文件就可以。 以本demo为例子, 运维在线上部署脚本,只需要修改env.prd.m4文件就可以。 预处理可以增加脚本的复用性。 不同环境,往往需要的功能不同。A环境需要X功能,B环境不需要X功能,那么怎么维护呢。 不用怕,有了m4条件分支,可以根据不同不配置,渲染出不同的结果。 m4基本在所有linux都已经安装好了,不用额外在安装依赖。 很多有经验的程序员,往往也不知道什么是m4, 其实大名顶顶的autoconf, 底层就依赖了m4。 m4难不难学? m4语法简单,语法强大,但是我们能用到的,基本不超过5个语法。 定义宏 下面是定义宏的语法,这样写之后,所有字符串ENV_LISTEN_IP都会被替换为127.0.0.1 define(ENV_LISTEN_IP, 127.0.0.1) 引用其他文件 有了引用,我们就不需要把所有功能放到一个大文件中。 include(<<src/loadmodule.m4>>) include(<<src/request.m4>>) include(<<src/relay.m4>>) include(<<src/per_branch_ops.m4>>) include(<<src/handle_nat.m4>>) include(<<src/missed_call.m4>>) ifelse(cond1,cond2, yes, no) 如果cond1和cond2相同,则展开第三个参数yes, 否则展开第四个参数no ifelse(ENV_ENABLE_NAT,yes,use nat, not use nat) 引号 引号,就是用来告诉m4, 引号里的内容应该看作是一个整体。 m4默认的引号是``’’, 看起来很怪异,很难从视觉上做配对。 所有有强迫症,或者视觉洁癖的人,会非常讨厌m4。 define(`ENV_LISTEN_IP', `127.0.0.1') 但是这个引号是可以修改的,我们用changequote去修改默认的引号, 将引号改为<<>> changequote(<<,>>) define(<<ENV_LISTEN_IP>>, <<127.0.0.1>>) 如何调试宏, 使用-dV 参数 m4 -dV opensips.m4 解决宏冲突问题 如果脚本里有个变量和m4的宏名字冲突,那么往往会出现一些怪异的问题。 m4早就想到了解决方案, 在执行m4时候,可以加上-P参数,m4所有内置的宏就会必须写成以m4_开头。 例如 m4_define(<<ENV_LISTEN_IP>>, <<127.0.0.1>>) m4 -P opensips.m4 m4的劣势 m4没有类似foreach的循环,但是可以通过m4的递归实现。 m4做简单的字符串替换,但是对于复杂字符串处理,m4的效率会比较低,而且语法比较复杂。 但是对于处理opensips的配置文件,m4则是刚刚好的完美工具。 有意思的几个扩展 检查宏是否未定义,或者是否为空字符串,空则报错退出 m4_divert(-1) m4_define(<<ASSERT_NOT_EMPTY>>,<< m4_ifelse($1,,<< m4_errprint(<<$1 is empty >>) m4_m4exit(1) >>,) >>) m4_divert(0)m4_dnl ASSERT_NOT_EMPTY(this_is_empty) foreach 循环 m4_changequote(<<,>>)m4_dnl m4_divert(-1) m4_define(<<X_FOREACH>>, <<m4_pushdef(<<$1>>)_foreach($@)m4_popdef(<<$1>>)>>) m4_define(<<_arg1>>, <<$1>>) m4_define(<<_foreach>>, <<m4_ifelse(<<$2>>, <<()>>, <<>>, <<m4_define(<<$1>>, _arg1$2)$3<<>>$0(<<$1>>, (m4_shift$2), <<$3>>)>>)>>) m4_divert(0)m4_dnl X_FOREACH(x,(a.com,b.com,c.com), alias=udp:x ) 上面会输出 ...
【案例分享】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消息 ...