概述

WFB-ng 协议旨在标准化通过原始 WiFi 无线电进行远距离数据传输的通信方式。这里的”远距离”是指标准 802.11 ACK 机制无法工作的距离(超过约 200 米)。

该方案允许使用支持”原始包”传输的普通 WiFi 适配器,以高达 8Mbps(MCS #1 调制)的速度传输任意数据流,传输距离可达数十公里。目前主要基于 Realtek RTL8812AU 芯片的适配器。

应用场景

  • 机器人与地面站之间的通信
  • 业余卫星(CUBESAT)与地球的通信
  • 地面数字无线电通信

工作原理

标准 WiFi 传输范围的主要限制在于:接收方必须在严格规定的时间间隔内发送 ACK 确认包。当两个站点之间的距离超过约 200 米时,接收方无法及时确认数据包,导致数据传输失败。

部分 WiFi 适配器支持”原始模式”(raw mode),可以绕过标准的 802.11 协议栈来收发数据包,关闭 ACK 机制的要求。此时传输距离不再受限于 ACK 超时,仅取决于接收灵敏度和发射功率。但需要自行实现 MAC 层(介质访问控制层)。

协议描述

基本特性

  • 支持点对点链路
  • 每个节点可同时参与任意数量的链路
  • 每条链路拥有独立的加密密钥集
  • 最多支持 256 个单向数据流

MAC 地址格式

发送方 MAC 地址的后四个字节用于标识链路归属:

1
2
0x57, 0x42, 0xaa, 0xbb, 0xcc, 0xdd
^W ^B ^link_id(3 bytes) ^stream_id(1 byte)
  • 前两个字节为协议头 'W'(0x57)、'B'(0x42)
  • 中间三个字节为链路 ID(link id)
  • 最后一个字节为流编号(stream number)

第一个地址字节 0x57 的低两位被设置,表示该地址是多播且本地管理的。

数据流分配方案

范围 方向 用途
0 - 127 下行(飞行器 → 地面站) 视频、Mavlink、隧道等
128 - 255 上行(地面站 → 飞行器) 控制指令等

流编号分配:

  • 0 - 15: 视频流(默认:0)
  • 16 - 31: Mavlink 流(默认:16)
  • 32 - 47: 隧道流(默认:32)
  • 其他范围保留供将来使用

数据处理流程

1
原始 UDP 数据包 → FEC 编码(zfec) → ChaCha20-Poly1305 加密 → WiFi 无线电包发送

无线数据包格式

协议定义两种包类型:

类型 packet_type 说明
数据包 1 包含 FEC 编码后的加密数据
会话包 2 包含加密的会话参数和会话密钥

数据包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Session key packet
typedef struct {
uint8_t packet_type; // packet_type = 2
uint8_t session_nonce[crypto_box_NONCEBYTES]; // random data
} __attribute__((packed)) wsession_hdr_t;

typedef struct{
uint64_t epoch; // 丢弃旧 epoch 的会话包
uint32_t channel_id; // (link_id << 8) + port_number
uint8_t fec_type; // FEC 类型
uint8_t k; // FEC k
uint8_t n; // FEC n
uint8_t session_key[crypto_aead_chacha20poly1305_KEYBYTES];
uint8_t tags[]; // 可选 TLV 属性
} __attribute__((packed)) wsession_data_t;

// Data packet
typedef struct {
uint8_t packet_type; // packet_type = 1
uint64_t data_nonce; // (block_idx << 8) + fragment_idx
} __attribute__((packed)) wblock_hdr_t;

typedef struct {
uint8_t flags;
uint16_t packet_size;
} __attribute__((packed)) wpacket_hdr_t;

IEEE 802.11 帧头

1
2
3
4
5
6
7
static uint8_t ieee80211_header[] = {
0x08, 0x01, 0x00, 0x00, // data frame, STA to DS via AP
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // receiver: broadcast
0x57, 0x42, 0xaa, 0xbb, 0xcc, 0xdd, // sender: channel_id
0x57, 0x42, 0xaa, 0xbb, 0xcc, 0xdd, // bssid: channel_id
0x00, 0x00, // (seq_num << 4) + fragment_num
};

完整包结构

1
2
3
4
5
6
7
8
9
10
11
radiotap_header:
ieee_80211_header:
数据包:
wblock_hdr_t { packet_type=1, nonce=(block_idx<<8)+fragment_idx }
wpacket_hdr_t { flags, packet_size }
data ← 使用 session key 加密和认证

会话包:
wsession_hdr_t { packet_type=2, nonce=random() }
wsession_data_t { epoch, channel_id, fec_type, k, n, session_key, TLV tags }
← 使用 crypto_box_easy(rx_publickey, tx_secretkey) 加密和认证

实现要点

参考实现

wfb-ng.org — WFB-NG 协议栈的参考实现(C + Python/Twisted),GPLv3 许可证。

加密机制

WFB-ng 使用 libsodium 进行数据流加密:

  1. TX 启动时生成新的会话密钥
  2. 使用公钥认证加密(cryptobox)加密会话密钥
  3. 每 SESSION_KEY_ANNOUNCE_MSEC(默认 1 秒)广播一次会话包
  4. 会话包使用 X25519 ECDH 密钥进行加密和认证
  5. 数据包使用 crypto_aead_chacha20poly1305_encrypt 加密,以 session key 和包索引作为 nonce

密码派生密钥(KDF)

对于低风险场景,可以从用户提供的密码派生密钥:

1
2
3
4
5
6
7
8
9
10
11
12
unsigned char salt[crypto_pwhash_argon2i_SALTBYTES] = 
{'w','i','f','i','b','r','o','a','d','c','a','s','t','k','e','y'};
unsigned char seed[crypto_box_SEEDBYTES * 2];

crypto_pwhash_argon2i(
seed, sizeof(seed), password, strlen(password), salt,
crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE, // 低 CPU 消耗
crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE, // 约 64MB RAM
crypto_pwhash_ALG_ARGON2I13); // 兼容旧版 libsodium

crypto_box_seed_keypair(drone_publickey, drone_secretkey, seed);
crypto_box_seed_keypair(gs_publickey, gs_secretkey, seed + crypto_box_SEEDBYTES);

测试向量: 密码为 secret password 时:

  • gs.key SHA1: cb8d52ca7602928f67daba6ba1f308f4cfc88aa7
  • drone.key SHA1: 7a6ffb44cebc53b4538d20bdcaba8d70c9cf4095

RX-Ring(接收环形缓冲区)

由于多个 RX 无线电有各自的内部队列,数据包可能乱序到达。RX-Ring 是一个循环缓冲区,按 FEC 块分组存储数据包:

  • rx_ring_front: 第一个已分配 FEC 块的索引
  • alloc_size: 已分配的块数

新包处理逻辑:

  1. 属于新的 FEC 块 → 在 RX ring 中分配(如果块已被处理则忽略)
  2. 属于已有的 FEC 块 → 添加到该块(如果包已处理则忽略)

成功解码所有分片后,输出并移除它之前的所有未完成块。当 RX ring 满时,覆盖头部的旧块。

这样可以保证输出的 UDP 数据包始终有序且无重复。

默认情况下 WFB-ng 将每个源 UDP 包封装到一个 WiFi 包中。但 Mavlink 包通常很小(小于 100 字节),单独发送会产生过多开销。优化的 Mavlink 模式会将多个 Mavlink 包聚合到一个 UDP 包中,直到达到 MAX_PAYLOAD_SIZE 或超过 mavlink_agg_in_ms 超时时间。

TX FEC 超时

默认情况下,如果少于 K 个数据包且没有新数据可用,WFB-ng 不会关闭 TX FEC 块。对于交互式协议或可变速率的数据流(如 Mavlink、IP 隧道),TX 可以发送带有 WFB_PACKET_FEC_ONLY 标志的空包来关闭非空的 FEC 块。或者可以使用 K=1 的 FEC 配置。