对 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_CFLAGSiperf3_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/iperf3src/.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 文档 来看,基本的调用模式应该是这样的:

  1. 创建并设置 test 结构体。
  2. 以 client/server 方式运行 test。
  3. 通过回调函数获得 test 运行状态。
  4. 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(),本质上,测试的流程是:

  1. Client 初次连接到 Server,建立一个 control_socket 进行通信,用以传送 cookie 和 state 信息。
  2. 在 state 切换到特定状态(TEST_RUNNING)时,Client 会多次连接到 Server,收发 stream。
  3. 结束时,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)    	
  1. 在 iperf_accept() 中,server 无法同时完成多个测试,因此如果有 client 正在测试,server 会拒绝新 client 连接。
  2. client 和 server 都会使用 timer 定时显示报告。

虽然已经使用 rust 尝试实现了少部分功能,但考虑到实际需求中参数变化较多,对应功能的实现比较繁琐,因此未继续进行。

简单总结

研究初始目标是要在 Windows 下使用 iperf3,经过分析,可以有以下几种方式:

  1. 使用进程调用方式,运行 iperf3 的可执行程序并分析输出(输出格式可以选择 json)。

  2. 编译出 iperf3 在 Windows 下的静态/动态库。

  3. 将 iperf3 移植到 Windows 下。

  4. 依照 iperf3 协议重新实现其客户端/服务端。

方法 1 是最容易实现的,也是最终选择的方式。

其他方法都有各自的优势,也并非完全不可行,但相对于方法 1 而言,成本和收益不成正比。

所以在实际项目中,我最终选择使用方法 1。

Top