用 SIPp 给 FreeSWITCH 做测试:注册、呼入、呼出、桥接、会议
Posted on in Tech
| Abstract | 用 SIPp 给 FreeSWITCH 做测试:注册、呼入、呼出、桥接、会议 |
|---|---|
| Authors | Walter Fan |
| Category | Tech |
| Version | v1.0 |
| Updated | 2026-11-03 |
| License | CC-BY-NC-ND 4.0 |
大纲
展开看看
- 一句话:SIPp 是一份 XML 脚本模拟成千上万个话机的 SIP 流量生成器。给 FreeSWITCH 做测试,就是把注册、呼入、呼出、桥接、会议五类场景各写一份脚本压过去。
- 先搞清角色:SIPp 既能当 UAC(主叫话机)也能当 UAS(被叫服务器)。谁发第一个包,谁就是 UAC。测 FreeSWITCH 时 SIPp 几乎总是 UAC。
- 三个关键字撑起整套脚本:
[call_id]、[service]、[authentication ...]。认证必须两段式——先收 401,再带着摘要重发。 - 注册:批量注册靠 CSV 注入账号。-r/-rp 控制速率,逐步加压找到 FreeSWITCH 的注册天花板。
- 呼入(dial in):SIPp 当 UAC 向 FreeSWITCH 发 INVITE,打到 9196 回音或 9664 音乐。带媒体就得
play_pcap_audio放一段 G.711 PCAP。 - 呼出(dial out):SIPp 起 UAS 冒充运营商/网关,FreeSWITCH 经 gateway 往外呼,SIPp 收 INVITE 并应答。
- 桥接(bridge):一个 SIPp 当主叫、另一个 SIPp 当被叫,FreeSWITCH 在中间
bridge。两端媒体都要有声音才算真通。 - 会议(conference):多路 SIPp 并发打进同一个
conference,验证混音、并发上限和 CPU。 - 收尾:SIPp 的统计屏和
-trace_err是第一诊断面;分不清是 SIPp 的锅还是 FreeSWITCH 的锅时,抓包和看sofia日志才是终审。
功能写完,单机手工拨一通,echo 听见自己声音——很多人到这就觉得"通了"。可线上从来不是一通一通来的。早高峰几千台话机同一分钟抢注册,座席此起彼伏地拨入拨出,还有会议室里十几路同时混音。手工测试在这种量级面前毫无意义。
你需要的是一个能同时假扮几千台话机的东西,按你编好的剧本自动发 SIP、自动应答、自动统计成功率和时延。SIPp 就是这个东西。
SIPp 的全部语法有自己的 DTD、字段几十个,这里不展开;注册、呼入、呼出、桥接、会议这五类场景,每类一份改掉 IP 就能跑的脚本,再加一张"出问题先看哪儿"的排查清单,够压测用了。
下面的脚本结构参照 SIPp 官方 docs/ 下的示例和 FreeSWITCH 源码树里 src/mod/endpoints/mod_sofia/test/sipp-scenarios/ 的官方测试脚本,命令行参数以 SIPp master 分支为准。FreeSWITCH 配置以默认 vanilla 安装(扩展 1000–1009、密码 1234、回音 9196)为例——这组默认密码只能用在测试机,上线前务必换掉。
先分清 UAC 和 UAS,别一上来就写错脚本
SIPp 不是只会打电话。它能同时扮演两个角色,搞混了脚本就从第一行开始错。
- UAC(User Agent Client):主动发起的一方,相当于拨号的话机。脚本第一个动作是
<send>一个 INVITE 或 REGISTER。 - UAS(User Agent Server):被动应答的一方,相当于接电话的服务器。脚本第一个动作是
<recv>等着别人发请求。
记一句话就够:谁发第一个包,谁就是 UAC。
测 FreeSWITCH 时,大多数场景 SIPp 都是 UAC——它扮演话机,FreeSWITCH 是被测的服务器。只有一个例外是呼出(dial out):FreeSWITCH 主动往外呼,这时 SIPp 得反过来当 UAS,冒充运营商的网关接住这通呼叫。
脚本里反复出现的几个关键字先认一下,后面不再解释:
| 关键字 | 含义 |
|---|---|
[local_ip] / [local_port] |
SIPp 自己的地址和端口,由命令行 -i 决定 |
[remote_ip] / [remote_port] |
被测目标(FreeSWITCH)的地址,由命令行末尾参数决定 |
[service] |
被叫号码,命令行 -s 指定,比如 -s 9196 |
[call_id] |
SIPp 自动生成的唯一 Call-ID,UAC 模式必须用它 |
[authentication username=... password=...] |
认证摘要占位符,SIPp 收到 401 后自动算出 Authorization 头 |
[field0] / [field1] |
从 CSV 文件注入的第 0、1 列,配合 -inf 使用 |
场景一:注册(register)——批量话机抢注册
最简单也最常被忽视的压测。几千台话机重启、网络抖动恢复,会在很短时间里涌来一大批 REGISTER。FreeSWITCH 的 Sofia 模块扛不扛得住这个洪峰,手工测不出来。
FreeSWITCH 的注册是带认证的两段式:第一次 REGISTER 不带密码,服务器回 401 Unauthorized 并给一个 nonce;SIPp 拿 nonce 算出摘要,第二次 REGISTER 带上 Authorization 头,服务器才回 200 OK。脚本必须老老实实走这两步,少一步就注册失败。
sequenceDiagram
participant S as SIPp (UAC)
participant FS as FreeSWITCH (Sofia)
S->>FS: REGISTER (无认证, CSeq 1)
FS-->>S: 401 Unauthorized (WWW-Authenticate: nonce)
S->>FS: REGISTER (带 Authorization 摘要, CSeq 2)
FS-->>S: 200 OK (Expires)
Note over S,FS: 少走第一步, 或 CSeq 不递增 → 注册失败
scenario 文件 register.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAC Register with auth">
<!-- 第一次 REGISTER:不带认证 -->
<send retrans="500">
<![CDATA[
REGISTER sip:[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: <sip:[field0]@[remote_ip]>;tag=[pid]SIPpTag00[call_number]
To: <sip:[field0]@[remote_ip]>
Call-ID: [call_id]
CSeq: 1 REGISTER
Contact: <sip:[field0]@[local_ip]:[local_port]>
Max-Forwards: 70
Expires: 300
Content-Length: 0
]]>
</send>
<!-- 服务器要求认证 -->
<recv response="401" auth="true"/>
<!-- 第二次 REGISTER:带上摘要,CSeq 要递增 -->
<send retrans="500">
<![CDATA[
REGISTER sip:[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: <sip:[field0]@[remote_ip]>;tag=[pid]SIPpTag00[call_number]
To: <sip:[field0]@[remote_ip]>
Call-ID: [call_id]
CSeq: 2 REGISTER
Contact: <sip:[field0]@[local_ip]:[local_port]>
[authentication username=[field0] password=[field1]]
Max-Forwards: 70
Expires: 300
Content-Length: 0
]]>
</send>
<recv response="200"/>
</scenario>
注意两个细节:recv 上的 auth="true" 告诉 SIPp 把收到的 WWW-Authenticate 挑战缓存下来;第二次 send 里的 [authentication ...] 占位符会自动展开成完整的 Authorization 头。两者缺一不可。CSeq 也要从 1 递增到 2,否则 FreeSWITCH 会当成重传。
账号从 CSV 注入 accounts.csv:
SEQUENTIAL
1000;1234
1001;1234
1002;1234
1003;1234
1004;1234
1005;1234
1006;1234
1007;1234
1008;1234
1009;1234
第一行 SEQUENTIAL 是读取顺序(也可以是 RANDOM),后面每行两列对应 [field0](用户名)和 [field1](密码)。
跑起来:
# 每秒 1 个注册,共 10 个,先确认脚本本身是对的
sipp -i 10.0.0.20 -sf register.xml -inf accounts.csv \
10.0.0.10:5060 -r 1 -rp 1000 -m 10 -aa -trace_err
# 确认无误后加压:每秒 30 个注册
sipp -i 10.0.0.20 -sf register.xml -inf accounts.csv \
10.0.0.10:5060 -r 30 -rp 1000 -aa -trace_err
-r 30 -rp 1000 表示"每 1000 毫秒发 30 个",也就是 30 次/秒。-aa 让 SIPp 自动忽略 FreeSWITCH 注册后推来的 NOTIFY(否则日志里会刷一堆无关告警)。-trace_err 把错误单独写到一个 _errors.log 文件,方便事后看。
压测的正确姿势是逐步加压:从 -r 1 开始确认流程对,然后 10、30、100……每一档盯着统计屏上的成功率和响应时延,直到 FreeSWITCH 开始丢包或时延飙升。那个拐点就是你这台机器的注册天花板。拿它和业务需求对比,不够就调 Sofia 参数、加内存和 CPU。
场景二:呼入(dial in)——SIPp 拨进来
这是最经典的 UAC 场景:SIPp 扮演话机,向 FreeSWITCH 发 INVITE,打到一个会自动应答的号码。FreeSWITCH vanilla 配置里现成的靶子有两个:9196(回音,把你的声音原样播回)和 9664(放保留音乐)。
sequenceDiagram
participant S as SIPp (UAC)
participant FS as FreeSWITCH (echo 9196)
S->>FS: INVITE (SDP offer)
FS-->>S: 100 Trying (可选)
FS-->>S: 180 Ringing (可选)
FS-->>S: 200 OK (SDP answer)
S->>FS: ACK
S-->>FS: RTP (play_pcap_audio)
FS-->>S: RTP (echo 原样播回)
Note over S,FS: 不发 RTP → 信令通但"没声音"
S->>FS: BYE
FS-->>S: 200 OK
FreeSWITCH 这边不用改——默认 dialplan 里 conf/dialplan/default.xml 已经有这两条:
<extension name="echo">
<condition field="destination_number" expression="^9196$">
<action application="answer"/>
<action application="echo"/>
</condition>
</extension>
不带媒体的 INVITE(只测信令) uac_invite.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAC dial in">
<send retrans="500">
<![CDATA[
INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:[field0]@[local_ip]:[local_port]>;tag=[pid]SIPpTag01[call_number]
To: <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: <sip:[field0]@[local_ip]:[local_port]>
Max-Forwards: 70
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
]]>
</send>
<recv response="100" optional="true"/>
<recv response="180" optional="true"/>
<recv response="200" rtd="true"/>
<send>
<![CDATA[
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:[field0]@[local_ip]:[local_port]>;tag=[pid]SIPpTag01[call_number]
To: <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Contact: <sip:[field0]@[local_ip]:[local_port]>
Max-Forwards: 70
Content-Length: 0
]]>
</send>
<!-- 通话保持 10 秒 -->
<pause milliseconds="10000"/>
<send retrans="500">
<![CDATA[
BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:[field0]@[local_ip]:[local_port]>;tag=[pid]SIPpTag01[call_number]
To: <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 BYE
Contact: <sip:[field0]@[local_ip]:[local_port]>
Max-Forwards: 70
Content-Length: 0
]]>
</send>
<recv response="200"/>
</scenario>
[peer_tag_param] 是 SIPp 从 200 OK 的 To 头里抓回来的对端 tag——ACK 和 BYE 必须带上它,否则 FreeSWITCH 认不出这是同一个 dialog。
跑起来:
sipp -i 10.0.0.20 -sf uac_invite.xml -inf accounts.csv \
-s 9196 10.0.0.10:5060 -l 50 -r 10 -rp 1000 -m 500 -trace_err
-l 50 是最大并发 50 路,-m 500 是总共打 500 通。
但只有信令的测试是半成品。 上面这个脚本能跑通 INVITE→200→ACK→BYE,可 RTP 端口其实没发一个包。FreeSWITCH 这边会觉得这通话"没声音"。真要测媒体路径(以及 FreeSWITCH 的转码、录音、媒体超时逻辑),得让 SIPp 真的发 RTP。最省事的做法是放一段预录的 PCAP:
<!-- 放在 ACK 之后、pause 之前 -->
<nop>
<action>
<exec play_pcap_audio="pcap/g711a.pcap"/>
</action>
</nop>
<pause milliseconds="8000"/>
g711a.pcap 是 SIPp 源码树自带的 G.711 A-law 样本(在 pcap/ 目录下)。注意 SDP 里的 codec 要和 PCAP 对上——G.711 A-law 对应 RTP/AVP 8(PCMA),μ-law 对应 RTP/AVP 0(PCMU)。发 A-law 的 PCAP 却在 SDP 里写 PCMU,FreeSWITCH 会听到一堆噪音。另外用 PCAP 发媒体时 SIPp 通常需要 root 权限(它要直接操作 RTP socket)。
场景三:呼出(dial out)——FreeSWITCH 往外呼
这是唯一一个 SIPp 当 UAS 的场景。现实里 FreeSWITCH 通过 gateway 把电话呼到运营商/上游 SBC,你想在没有真实运营商的情况下测这条路,就让 SIPp 冒充那个上游——起一个 UAS,蹲在某个端口上,接住 FreeSWITCH 发过来的 INVITE 并回 200 OK。
sequenceDiagram
participant CLI as fs_cli / originate
participant FS as FreeSWITCH (gateway)
participant S as SIPp (UAS, 冒充运营商)
CLI->>FS: originate sofia/gateway/sipp_gw/91234
FS->>S: INVITE (经 gateway 出局)
S-->>FS: 180 Ringing
S-->>FS: 200 OK (SDP answer)
FS->>S: ACK
Note over FS,S: 方向反过来: FreeSWITCH 是主叫, SIPp 应答
FS->>S: BYE
S-->>FS: 200 OK
FreeSWITCH 这边配一个指向 SIPp 的 gateway(conf/sip_profiles/external/sipp_gw.xml):
<include>
<gateway name="sipp_gw">
<param name="proxy" value="10.0.0.20:5070"/>
<param name="register" value="false"/>
</gateway>
</include>
再配一条拨号规则,让某个号码走这个 gateway 出去:
<extension name="dial_out_to_sipp">
<condition field="destination_number" expression="^(9[0-9]{3})$">
<action application="bridge" data="sofia/gateway/sipp_gw/$1"/>
</condition>
</extension>
SIPp 这边起一个 UAS uas.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAS answer">
<recv request="INVITE"/>
<send>
<![CDATA[
SIP/2.0 180 Ringing
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag02[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag02[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:101 telephone-event/8000
]]>
</send>
<recv request="ACK" rtd="true"/>
<recv request="BYE"/>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Content-Length: 0
]]>
</send>
</scenario>
UAS 脚本里几乎所有头都用 [last_...] ——这是把收到的请求里的对应头原样回贴,这样 Via/From/Call-ID 自然对得上,不用自己拼。
先起 SIPp(它是被叫,得先就位),再从 FreeSWITCH 发起呼叫:
# 终端 1:SIPp 当 UAS 蹲守 5070 端口
sipp -i 10.0.0.20 -p 5070 -sf uas.xml -trace_err
# 终端 2:在 fs_cli 里发起一通外呼,destination 匹配上面那条规则
freeswitch@internal> originate sofia/gateway/sipp_gw/91234 &park
SIPp 的统计屏上出现一通被接听的呼叫,就说明 FreeSWITCH 的外呼链路(gateway 选路、SDP 协商、ACK 处理)是通的。要压测就把 originate 换成一个循环脚本,或者用 FreeSWITCH 的 fs_cli 批量发起。
场景四:桥接(bridge)——一进一出,FreeSWITCH 居中
前面三个场景,SIPp 要么只对着 FreeSWITCH 的内置应用(echo),要么单独测一个方向。但真实话务的核心是 A 打给 B,FreeSWITCH 把两条腿桥在一起。这个场景必须同时起两个 SIPp:一个当主叫(UAC),一个当被叫(UAS),FreeSWITCH 在中间做 bridge。
sequenceDiagram
participant A as SIPp 主叫 (UAC)
participant FS as FreeSWITCH (bridge)
participant B as SIPp 被叫 (UAS)
A->>FS: INVITE (拨 2000)
FS->>B: INVITE (bridge 出第二条腿)
B-->>FS: 200 OK
FS-->>A: 200 OK
A->>FS: ACK
FS->>B: ACK
A-->>FS: RTP 媒体
FS-->>B: RTP 媒体
B-->>FS: RTP 媒体
FS-->>A: RTP 媒体
Note over A,B: 两端都要放 PCAP, 双向都有声音才算真桥通
A->>FS: BYE
FS->>B: BYE
FreeSWITCH dialplan:让主叫拨某个号码时,桥接到被叫所在的 SIPp(conf/dialplan/default.xml):
<extension name="bridge_to_callee">
<condition field="destination_number" expression="^(2000)$">
<action application="bridge" data="sofia/external/2000@10.0.0.21:5080"/>
</condition>
</extension>
这里 10.0.0.21:5080 是被叫那个 SIPp UAS 的地址。
三方的启动顺序(被叫先就位,主叫最后发起):
# 终端 1:被叫侧 SIPp(UAS),蹲在 5080,收 INVITE 就应答并放 PCAP
sudo sipp -i 10.0.0.21 -p 5080 -sf uas_media.xml -trace_err
# 终端 2:主叫侧 SIPp(UAC),拨 2000,FreeSWITCH 会把它桥到被叫
sudo sipp -i 10.0.0.20 -sf uac_invite.xml -inf accounts.csv \
-s 2000 10.0.0.10:5060 -l 50 -r 10 -rp 1000 -m 500 -trace_err
其中 uas_media.xml 就是场景三的 uas.xml 再加一段 play_pcap_audio(应答后放 PCAP),让被叫侧也真的发 RTP。
桥接测试的关键在"双向都要有声音"。 只看 SIP 信令通过(两边都 200 OK)是不够的:FreeSWITCH 的 bridge 本质是把两条腿的 RTP 对接起来,只有两端 SIPp 都在发 PCAP 媒体流、而且在各自的统计里看到收到了对端的 RTP,这通桥接才算真通。很多人卡在这:信令全绿,但一端没放媒体,结果是"接通了但一方没声音"——这正是 bridge 测试要抓的 bug。加压时盯住 FreeSWITCH 的 CPU 和 RTP 端口范围(rtp-start-port/rtp-end-port),桥接是双倍 RTP,最容易先把媒体端口或 CPU 吃满。
场景五:会议(conference)——多路并发混音
会议是 bridge 的放大版:不是两条腿对接,而是 N 条腿进同一个房间混音。测的是 FreeSWITCH 的 mod_conference 在并发下的混音开销和人数上限。
sequenceDiagram
participant S1 as SIPp 腿 1
participant S2 as SIPp 腿 2
participant SN as SIPp 腿 N
participant FS as FreeSWITCH (conference 3000)
S1->>FS: INVITE → 进会议室
S2->>FS: INVITE → 进会议室
SN->>FS: INVITE → 进会议室
FS-->>S1: 200 OK / ACK
FS-->>S2: 200 OK / ACK
FS-->>SN: 200 OK / ACK
par 每条腿都发 RTP
S1-->>FS: RTP
S2-->>FS: RTP
SN-->>FS: RTP
end
FS-->>S1: 混音后 RTP
FS-->>S2: 混音后 RTP
FS-->>SN: 混音后 RTP
Note over S1,FS: -d 保持 60s 让 N 路同时在线; 不发 RTP 则无混音负载
FreeSWITCH dialplan(vanilla 默认就有类似的,拨 3000 进会议室):
<extension name="conference">
<condition field="destination_number" expression="^(3000)$">
<action application="answer"/>
<action application="conference" data="$1@default"/>
</condition>
</extension>
SIPp 这边直接复用场景二带 PCAP 的呼入脚本,把被叫号码指向会议室,并发数开大:
sudo sipp -i 10.0.0.20 -sf uac_invite_media.xml -inf accounts.csv \
-s 3000 10.0.0.10:5060 -l 20 -r 2 -rp 1000 -m 20 -d 60000 -trace_err
几个参数和前面不一样:
-l 20并发 20 路——会议靠的是并发,不是呼叫速率,所以这里要的是同时在线 20 路,而不是每秒打多少。-d 60000让每通呼叫在 INVITE 和 BYE 之间停 60 秒,保证这 20 路同时待在会议室里混音,而不是打进来就挂。- 每一路都要
play_pcap_audio放媒体,否则会议室里是 20 个哑巴,mod_conference根本没有混音负载,压不出真实开销。
压测时盯三样:FreeSWITCH 的 CPU(混音是纯计算,人越多越吃 CPU)、conference list 里的实际人数(对不对得上你发起的并发)、以及有没有人因为超时被踢。逐步把 -l 从 20 加到 50、100,找到单会议室的人数拐点。
出问题先看哪儿:SIPp × FreeSWITCH 排查清单
压测跑红了,第一反应往往是"FreeSWITCH 不行"。但很多时候是脚本写错或 SIPp 配置不对。按这个顺序查,能省掉大半冤枉路:
| 症状 | 大概率原因 | 先看哪儿 |
|---|---|---|
| 注册全部失败,连 401 都没有 | SIPp 发不出包/IP 端口不对 | -trace_err 日志、-i 地址对不对 |
| 收到 401 但拿不到 200 | 认证两段式没写对,或密码错 | recv 上有没有 auth="true"、CSeq 有没有递增 |
| INVITE 收到 403/407 | FreeSWITCH 要求认证但脚本没带 | profile 的 auth-calls、脚本是否走认证 |
| 信令全通但"没声音" | 根本没发 RTP | 脚本里有没有 play_pcap_audio |
| 有 RTP 但对端听着是噪音 | codec 和 PCAP 对不上 | SDP 里的 PCMA/PCMU 和 PCAP 一致吗 |
| 并发一高就大量超时/丢包 | 到了 FreeSWITCH 或机器的真实上限 | FreeSWITCH CPU、RTP 端口范围、Sofia 参数 |
| SIPp 报 RTP 相关权限错误 | 放 PCAP 需要 root | 用 sudo 跑 |
两个终审工具:SIPp 的统计屏(按 1–9 切换不同视图,看成功/失败计数和响应时延分布)和 FreeSWITCH 的 sofia 日志(fs_cli 里 sofia global siptrace on,或直接 sofia loglevel all 9)。分不清是谁的锅时,两边同时抓包(SIPp 侧和 FreeSWITCH 侧各 tcpdump 一份),用 Wireshark 的 SIP/RTP 流视图对一下,包到底卡在哪一跳一目了然。
总结:先证明脚本对,再逐步加压找拐点
用 SIPp 测 FreeSWITCH,难的从来不是某个单独的 XML,而是三件事:分清 SIPp 当 UAC 还是 UAS、把认证两段式写对、以及记得真的发 RTP 媒体。这三样踩过一遍,五类场景的脚本结构其实高度相似,改改号码和并发参数就能复用。
落地就两步,顺序别反:
- 先用
-r 1 -m 10把脚本跑通,确认流程对、认证过、媒体有声音。脚本不对就加压,是在用错误的子弹打靶。 - 再逐步加压找拐点,每一档盯住统计屏的成功率和时延,直到 FreeSWITCH 开始丢包。那个拐点就是这台机器在这个场景下的真实容量——拿它和业务需求对比,才知道要不要调参、加机器。
手工拨通一通,只证明"能通";SIPp 压出拐点,才知道"能扛多少"。 后者才是上线前真正要回答的问题。
参考与延伸
SIPp 本体
- SIPp 官方参考文档:命令行参数、关键字、actions、PCAP Play、统计屏的权威出处。本文的
[authentication]、[last_*]、play_pcap_audio用法都以此为准。 - SIPp 源码仓库:
docs/目录下有uac.xml、uac_pcap.xml、uas.xml等内置场景的完整 XML,是自己写脚本最好的模板;pcap/下有现成的g711a.pcap媒体样本。 sipp -sd uac:把内置场景直接导出成 XML,改一改就能用,比从空白文件开始快得多。
FreeSWITCH 侧
- FreeSWITCH 源码里的 sipp 测试脚本:官方自己就用 SIPp 测 Sofia,
uas_register.xml、conf-sipp/freeswitch.xml是注册认证和配套 profile 的权威样例。 - FreeSWITCH 官方文档:dialplan、
bridge、conference、Sofia profile 参数(rtp-start-port、auth-calls等)的查阅入口。
上手教程与现成脚本
- os11k/sipp2freeswitch:一套可直接克隆的注册 + 呼叫脚本加 CSV 账号文件,适合先跑通再改。
- NickSkrypchenko/sipp-examples:re-INVITE、主/被动媒体、RTP echo 等进阶场景的 XML 示例。
- Lorenzo Miniero 在 KamailioWorld 的 《Load Testing of SIP and WebRTC Infrastructures》:从 SIP 到 WebRTC 的压测方法论,以及 Sippy Cup 这类 SIPp 辅助工具。
配套诊断工具
- Wireshark:SIP/RTP 流视图(Telephony → VoIP Calls / RTP Streams)是定位"卡在哪一跳"的终审工具,两边同时抓包对比最有效。
sngrep:终端里实时看 SIP 对话的轻量工具,压测时比翻日志快。
全文思维导图
@startmindmap
<style>
mindmapDiagram {
node {
BackgroundColor #F8F9FA
RoundCorner 10
Padding 10
FontSize 13
}
:depth(0) {
BackgroundColor #1E3A5F
FontColor white
FontSize 18
FontStyle bold
}
:depth(1) {
FontSize 15
FontStyle bold
}
:depth(2) {
FontSize 13
}
}
</style>
* SIPp 测 FreeSWITCH
** 基础概念
*** UAC = 发第一个包
*** UAS = 等着收包
*** 认证两段式
** 注册 register
*** 401 → 带摘要重发
*** CSV 注入账号
*** -r/-rp 逐步加压
** 呼入 dial in
*** SIPp 当 UAC
*** 打 9196 回音
*** play_pcap_audio 发媒体
** 呼出 dial out
*** SIPp 当 UAS
*** 冒充运营商网关
*** gateway + bridge
** 桥接 bridge
*** 两个 SIPp
*** 双向都要有声音
*** 盯 RTP 端口/CPU
** 会议 conference
*** 多路并发
*** -l 控并发 -d 保持
*** 盯混音 CPU
** 排查
*** 统计屏 + trace_err
*** sofia 日志
*** 两边抓包
@endmindmap

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 欢迎在我的个人网站 https://www.fanyamin.com 访问原文并评论。