对 iperf3 的一些研究
2023 年 02 月 25 日
iperf3 是一个流行度很高的网络性能测试工具。近来因为工作需要,对 iperf3 进行了一些研究。
版本差异问题
截止到 2023 年 2 月,iperf3 源码最新的版本是 3.12。但官方释出的 Windows 下的最高版本只有 3.1.3。
从实际使用的情况来看,自行编译的 3.12 版本在 UDP 测试时,Server 端运行于 127.0.0.1 时,会出现丢包现象。与之相对的,3.1.3 运行没有问题。
因此项目中实际使用时,建议选择 3.1.3。
Linux 下使用 musl 工具链交叉编译和运行
有个开发板使用的是 musl-arm 工具链,因此小小研究了一下交叉编译方法。
首先要从 musl.cc 下载工具链,我放在了 $HOME/musl
。
然后从 git 下载源码。checkout 到 3.1.3 分支。
由于编译器的限制,一开始编译会失败,需要做一些修改,包括:
- cherry-pick 此提交,
git cherry-pick a8ee9c650b3efe4453a69ceebe4d71
,然后解决冲突。 - 修改 src/Makefile.in,去掉
iperf3_profile_CFLAGS
和iperf3_profile_LDFLAGS
中的-pg
。参考文档在这里。
接下来可以编译了:
./configure --host=arm-linux-musleabi --prefix=$HOME/iperf/install CC=$HOME/musl/arm-linux-musleabi-cross/bin/arm-linux-musleabi-cc
make
将生成的 src/.libs/iperf3
和 src/.libs/libiperf.so.0.0.0
推送到目标板,前者需要 chmod +x
,后者需要改名为 libiperf.so.0
或者加个符号链接。
在目标板上运行:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./iperf3 --help
Windows 下的编译和链接问题
iperf3 在 Linux 下是以静态库(libiperf.a)和可执行程序的形式发布的,而在 Windows 下发布的只有可执行程序。
由于 iperf3 源码中使用了大量 Linux 特有的头文件,因此无法直接在 Windows 下直接编译,需要使用 cygwin 或者 msys2。
首先安装必备软件,完成环境配置后,下载源码并执行如下命令。
# 在 iperf 源码根目录下执行
libtoolize --force
aclocal
autoheader
automake --force-missing --add-missing
autoconf
./configure
make
此时可以得到 iperf 源码目录下的以下生成档和源文件:
- src/.libs/iperf3.exe
- src/.libs/libiperf.a
- src/iperf_api.h
注意,由于借助了 cygwin 或者 msys2 作为转换层,因此可执行程序还依赖其他一些库才能正常执行,这些库需要与应用放在同一目录。
- cygwin:
- /usr/lib/cygwin1.dll
- msys2:
- /usr/lib/w32api/libws2_32.a
- /usr/lib/libmsys-2.0.a
- /usr/bin/msys-2.0.dll
注:对于 3.12 版本,加入了 SSL 支持。因此,如果编译时开启了 iperf_config.h 中的 HAVE_SSL 选项,依赖库还需要加入 libcrypto、libz 和 libstdc++ 等。
由于 Windows 和 Linux 的静态库格式是一致的。因此 libiperf.a 其实是可以直接在 Windows 下使用的。例如 iperf_get_iperf_version() 这样的简单接口,没有引入复杂的参数和返回值,直接调用是可以成功的。
但是,iperf_api.h 依赖于 Linux 下的一些 header 文件中的复杂结构体,其接口设计大量依赖 iperf_test 结构体指针,而 iperf_test 结构体内部使用了函数指针和 FD_SET,导致非 C 语言的外部调用(如使用 rust + tauri)难以直接使用,需要对接口进行至少一层封装。由于封装的复杂性和其带来的价值不成正比,因此没有深入研究。
另外,由于以上方式依赖 cygwin 或 msys 的特定库,考虑到 GPL 的传染性,实际软件发布也有潜在风险。
注:有开发者通过在 Windows 下替换 iperf3 依赖接口的方式,提供了 iperf3 移植到 Windows 的方法。由于此方法限制了 iperf3 源码版本,并且默认也不提供 libiperf 的编译,因此没有深入研究。
原理分析
有考虑直接使用 rust 重构 iperf3,因此对源码进行了大量阅读。分析如下:
调用模式
从 API 文档 来看,基本的调用模式应该是这样的:
- 创建并设置 test 结构体。
- 以 client/server 方式运行 test。
- 通过回调函数获得 test 运行状态。
- test 结束后,获得输出和错误报告。
API 说明
调用 API 简单说明:
-
iperf_test 结构体定义于 iperf.h,描述了测试相关的变量。结构体的处理函数如下:
- iperf_new_test 函数生成 iperf_test 结构体。
- iperf_free_test 函数销毁 iperf_test 结构体。
- iperf_set 系列函数调整 iperf_test 结构体中的数据。
- iperf_get 系列函数获取 iperf_test 结构体中的数据。
-
iperf_run_client:以 client 模式运行 iperf_test。
-
iperf_run_server:以 server 模式运行 iperf_test。
-
iperf_reset_test:重置 iperf_test。
-
iperf_get_test_outfile:获取文件输出。
-
cpu 绑定相关函数:
- sched_setaffinity,Linux 接口。
- cpuset_setaffinity,FreeBSD 接口。
- SetProcessAffinityMask,Windows 接口。
核心代码交互流程时序
核心代码位于 iperf_run_client() / iperf_run_server(),本质上,测试的流程是:
- Client 初次连接到 Server,建立一个 control_socket 进行通信,用以传送 cookie 和 state 信息。
- 在 state 切换到特定状态(TEST_RUNNING)时,Client 会多次连接到 Server,收发 stream。
- 结束时,Client 主动通知 state 切换,断开连接,结束测试。
交互流程时序如下图:
sequenceDiagram
participant Client
participant Server
Note over Server: iperf_server_listen()
Note over Client: iperf_connect()
Note over Server: iperf_accept()
Client ->> Server: cookie(36 字节随机字符串)
Server ->> Client: state(PARAM_EXCHANGE)
Note over Client, Server: > iperf_exchange_parameters()
Note over Client: >> send_parameters()
Note over Server: >> get_parameters()
Note over Server: >> iperf_tcp_listen() = test->protocol->listen()
Server ->> Client: state(CREATE_STREAMS)
Note over Server: > iperf_setaffinity() (可选)
Note over Client, Server: > on_connect()
Note over Client: iperf_handle_message_client() -> iperf_create_streams() 生成文件
Server ->> Client: state(TEST_START)
Note over Client, Server: iperf_init_test()
Note over Server: > create_server_timers()
Note over Server: > create_server_omit_timer()
Note over Server: > iperf_create_send_timers() (可选)
Note over Client: > create_client_timers()
Note over Client: > create_client_omit_timer()
Note over Client: > iperf_create_send_timers()(可选)
Server ->> Client: state(TEST_RUNNING)
Note over Server: iperf_recv() / iperf_send()
Note over Client: iperf_send() / iperf_recv()
Client ->> Server: state(TEST_END)
Note over Server: test->stats_callback()
Note over Server: test->reporter_callback()
Server ->> Client: state(EXCHANGE_RESULTS)
Note over Client, Server: iperf_exchange_results()
Server ->> Client: state(DISPLAY_RESULTS)
Note over Client: iperf_client_end()
Client ->> Server: state(IPERF_DONE)
- 在 iperf_accept() 中,server 无法同时完成多个测试,因此如果有 client 正在测试,server 会拒绝新 client 连接。
- client 和 server 都会使用 timer 定时显示报告。
虽然已经使用 rust 尝试实现了少部分功能,但考虑到实际需求中参数变化较多,对应功能的实现比较繁琐,因此未继续进行。
简单总结
研究初始目标是要在 Windows 下使用 iperf3,经过分析,可以有以下几种方式:
-
使用进程调用方式,运行 iperf3 的可执行程序并分析输出(输出格式可以选择 json)。
-
编译出 iperf3 在 Windows 下的静态/动态库。
-
将 iperf3 移植到 Windows 下。
-
依照 iperf3 协议重新实现其客户端/服务端。
方法 1 是最容易实现的,也是最终选择的方式。
其他方法都有各自的优势,也并非完全不可行,但相对于方法 1 而言,成本和收益不成正比。
所以在实际项目中,我最终选择使用方法 1。