IEEE 1905.1 协议详解(阶段 1 笔记)
学习目标:搞懂 EasyMesh 所有上层消息赖以传输的底层协议——IEEE 1905.1 汇聚层。
方法:协议原理为主线,prplMesh 源码作佐证(代码只用来印证"协议字段长这样、怎么处理")。
代码路径:prplMesh/framework/transport/ieee1905_transport/、prplMesh/framework/tlvf/
0. 一句话理解 IEEE 1905.1
IEEE 1905.1 是一个"汇聚层"(Convergence Layer)协议:它在 Ethernet、Wi-Fi、Coax、PLC 等多种异构底层链路之上,抽象出一套统一的控制面消息机制,让多台设备能在一个"逻辑网络"里互相发现、交换拓扑、协同管理。
EasyMesh 并不是直接跑在 Wi-Fi 上的协议——EasyMesh 的所有消息(Topology Discovery、AP Auto-Config、Steering…)都封装在 1905.1 CMDU 里,再由 1905.1 汇聚层通过以太网/Wi-Fi 等链路传输。
协议位置示意:
应用层:EasyMesh(Controller/Agent 业务逻辑) ↓ 封装为 汇聚层:IEEE 1905.1(CMDU + TLV) ← 本笔记讲这一层 ↓ 承载于 链路层:Ethernet / Wi-Fi / Coax / PLC
1. 核心概念
1.1 AL MAC(AL = Abstraction Layer)
每个 1905.1 设备有一个 AL MAC 地址,它是该设备在 1905.1 逻辑网络里的"门牌号"。注意:
- AL MAC 不等于任何一个物理网卡的 MAC,它是本机 1905.1 汇聚层(Abstraction Layer) 的逻辑地址——不是 Controller 分配的
- 这里的「汇聚层」= IEEE 1905.1 协议本身那一层(Convergence / Abstraction Layer),不是 EasyMesh 的 Controller 角色
- 每个设备自己都有汇聚层,所以每台跑 1905.1 的设备(无论 Controller 还是 Agent)都有自己的一个 AL MAC
- 一个设备只有一个 AL MAC,但可能有多个底层接口(多个以太网口、多个 Wi-Fi radio)
- EasyMesh 通信寻址用的是 AL MAC,不是底层接口 MAC
- 实现上 AL MAC 常取 bridge 口 MAC 或某个主接口 MAC(看起来“像”物理 MAC),但语义上它代表的是整台设备的 1905 逻辑身份,不是“某一个网卡”
ALID(AL Identifier):通常与 AL MAC 一致,用于唯一标识一个 1905.1 实体。
┌─ 设备 A(主路由,Controller+Agent)─┐
│ AL MAC = AA:AA:AA:AA:AA:AA ← 整机门牌号(1 个)
│ eth0 MAC / wlan0 MAC / wlan1 MAC … ← 多个物理口 MAC
└────────────────────────────────────┘
┌─ 设备 B(扩展器,纯 Agent)─────────┐
│ AL MAC = BB:BB:BB:BB:BB:BB ← 自己的门牌号(自己的汇聚层)
│ eth0 MAC / wlan0 MAC …
└────────────────────────────────────┘
Controller 用 AL MAC 跟 Agent 说话:发往 BB:BB:…,不是发给某个 radio 口 MAC。
1.2 多链路抽象
1905.1 的精髓:屏蔽底层差异。对上层而言,"发一条消息给邻居 AL MAC"就够了,不用关心这条消息实际走了以太网还是 Wi-Fi。汇聚层负责:
- 在每个底层接口上收发 CMDU
- 维护"AL MAC ↔ 哪个接口可达"的映射
- 跨链路转发(一条消息从 Wi-Fi 进来,可能从以太网转出去)
1.3 多播地址
1905.1 用一个固定的组播 MAC 地址做广播发现:
01:80:C2:00:00:13
所有 1905.1 控制消息(如 Topology Discovery)都发到这个组播地址。注意 IEEE 802.1D 的严格保留组播段是 01:80:C2:00:00:00 ~ 01:80:C2:00:00:0F(最后字节 0x00~0x0F,即 0~15),网桥对这一段不转发;而 1905.1 用的是 0x13(=19),已超出 0x00~0x0F 保留段,所以 Linux bridge 默认会把它当普通组播泛洪到所有端口。这会破坏 1905.1 自己的 relay 转发机制(见第 7 节),因此必须用 ebtables 规则阻止 bridge 泛洪,把转发权交给协议层。
代码佐证:prplMesh 把这个地址定义在头文件里
// ieee1905_transport.h static constexpr sMacAddr ieee1905_multicast_addr = { .oct = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x13}};并在注释里提醒:IEEE1905 组播包不应被 Linux 网桥泛洪,需要打 ebtables 规则屏蔽:
ebtables -t filter -I FORWARD 1 -p 0x893a --destination 01:80:c2:00:00:13 -j DROP规则拆解:
-t filter:操作 filter 表(网桥转发过滤的默认表)-I FORWARD 1:在 FORWARD 链最前面插入规则(最高优先级)。FORWARD 链管的是"bridge 从一个口转发到另一个口"的包-p 0x893a:匹配 EtherType=0x893a 的帧(1905.1 CMDU)--destination 01:80:c2:00:00:13:匹配目的 MAC = 1905.1 组播地址-j DROP:命中即丢弃,不让 bridge 跨口转发为什么要这条规则——分清两种"转发":
- Linux bridge 的泛洪转发(要禁止):bridge 收到组播包默认从所有其他口发出去,是无脑复制,不可控。
- 1905.1 协议层的 relay 转发(要保留):靠 CMDU Header 的
RelayIndicator位精确控制——仅对组播01:80:C2:00:00:13有效:relay=1 才跨多跳中继,relay=0 只上交本机、不外传中继(单播不受 relay 影响,见第 7.4 节)。如果让 bridge 先泛洪,协议层的 relay 机制就形同虚设,还会产生重复包风暴。这条规则的作用是把转发权从 bridge 手里夺过来交给协议层:bridge 只负责把包上交本机(走本机接收路径),不负责跨口转发(FORWARD 链被 DROP)。
比方:假设路由器上
br0桥接了eth0(有线口)和wlan0(Wi-Fi 口)。没有这条规则时,Wi-Fi 口收到的 1905.1 组播包会被 bridge 无脑复制到 eth0 也发一份(泛洪);打了这条规则后,Wi-Fi 口收上来的包不再被 bridge 转发到 eth0,而是只上交给本机 1905.1 协议层处理——由协议层看 relay 位决定要不要从 eth0 再发出去。即"bridge 不掺和转发,转发归协议层管"。完整对比(
br0桥接eth0+wlan0,wlan0 收到 1905.1 组播包):没有 ebtables 规则: 有 ebtables 规则: wlan0 收到 1905.1 组播包 wlan0 收到 1905.1 组播包 → bridge 泛洪:eth0 也收到一份 ❌ → bridge FORWARD 命中 DROP:不复制到 eth0 ✓ → 本机协议层也收到一份 → 本机协议层收到一份 → 协议层 relay=1 又从 eth0 发一次 → 协议层看 relay=1,自己从 eth0 精确发一次 → eth0 收到 2 份,重复 ❌ → eth0 收到 1 份,干净受控 ✓两个易错细节澄清:
- 不是"直接丢给内核协议栈":bridge DROP 掉 FORWARD 链后,包并没有被完全丢弃,而是仍然会上交本机(本机接收路径)。本机的 1905.1 协议层(prplMesh 里是用户态进程)收到后再决定怎么处理。
- "不转发到其他口"是对的,但协议层可能会自己再发:bridge 不转发 ≠ 包就不出去了。协议层收到后如果
RelayIndicator=1,会自己从合适的口把包再发出去(受控中继)。所以最终包可能还是会从 eth0 出去,但这是协议层主动发的,不是 bridge 泛洪的。一句话总结:“bridge 闭嘴,转发归协议层说话”——bridge 只管把包递给本机,跨口转发的事由 1905.1 协议层按 relay 规则决定。
2. EtherType:0x893a
1905.1 CMDU 在以太网帧里用一个专用的 EtherType 标识:
EtherType = 0x893a
这是抓包时识别 1905.1 流量的关键。Wireshark 显示过滤器用 ieee1905,捕获过滤器用 ether proto 0x893a(或 eth.type == 0x893a)。
如果带 802.1Q VLAN 标签,帧结构是:dst(6) + src(6) + TPID(0x8100) + TCI(2) + EtherType(0x893a) + CMDU。
代码佐证:prplMesh 定义了带 VLAN 的以太网头结构
// ieee1905_transport.h static constexpr uint16_t ieee_8021q_protocol_id = 0x8100; struct ether_header_vlan { uint8_t ether_dhost[ETH_ALEN]; uint8_t ether_shost[ETH_ALEN]; uint16_t tpid; // 0x8100 uint16_t tci; // VID + DEI + PCP uint16_t ether_type; // 0x893a } __attribute__((__packed__));
2.1 为什么没有 IP 也能通信(纯二层协议)
关键点:IEEE 1905.1 是纯粹二层以太网协议,完全不使用 IP / UDP / TCP,依靠 MAC 地址完成通信。
普通网络:IP(三层)→ 查 ARP 得 MAC(二层)→ 网卡发送。
1905.1:跳过 IP 层,直接跑在数据链路层:
- 目标地址用 MAC
- 广播/发现:固定组播 MAC
01:80:C2:00:00:13 - 单播回复:直接填对端硬件/接口 MAC 或 AL MAC(从收到报文的源地址学习),不需要 ARP
- 广播/发现:固定组播 MAC
- EtherType =
0x893A- 网卡/协议栈看到这个类型,交给 1905 协议栈处理,不送到 IP 协议栈
- 不需要 ARP
- ARP 是 IP 用来查 MAC 的;1905 直接用 MAC,没有 ARP 流程
通俗比喻:
- IP 通信:信封先写【IP 地址】,再查 MAC 交给网卡
- 1905 通信:信封直接写【MAC 硬件地址】,直接交给网卡发送
工程限制:不能跨三层 / 跨 VLAN 隔离域
⚠️ 重要限制:1905 二层帧不能跨路由器三层转发,也受 VLAN 广播域隔离影响。
设备必须在同一个二层广播域内才能互相发现;跨 VLAN / 三层网关时,1905 组播常被阻断,EasyMesh 搜不到 Agent。
实际工程坑:划分 VLAN 或开了过严的 IGMP/组播抑制后 EasyMesh 发现失败,根源往往是二层组播被隔离或丢弃——不是“没配 IP”。
Agent 上电找 Controller 的寻址示例(无 IP 参与)
1. Agent 构造以太网帧
DST = 01:80:C2:00:00:13(组播发现)
SRC = Agent 接口 MAC
EtherType = 0x893A
MessageType = 0x0007(AP_AUTOCONFIGURATION_SEARCH)
2. 交换机/桥在同一广播域内传播该组播帧(注意:Linux bridge 对 0x13
还需 ebtables 禁止泛洪,转发权归协议层,见第 1.3 节)
3. Controller 收到后,构造单播应答
DST = 收到报文的源 MAC(Agent)——不需要 IP,不需要 ARP
SRC = Controller 接口 MAC
EtherType = 0x893A
MessageType = 0x0008(AP_AUTOCONFIGURATION_RESPONSE)
MessageId 与请求相同(用于配对)
4. Agent 收到单播应答,完成发现
整个流程没有任何 IP 地址出现,所有寻址完全依靠二层 MAC。
2.2 Wireshark 过滤模板(入门常用)
| 目的 | 过滤器 |
|---|---|
| 全部 1905 帧 | eth.type == 0x893a 或 ieee1905 |
| Agent 搜 Controller | eth.type == 0x893a && ieee1905.cmdu.type == 0x0007 |
| Topology Discovery | ieee1905.cmdu.type == 0x0000 |
| EasyMesh 扩展消息(0x8000 起) | ieee1905.cmdu.type >= 0x8000 |
| 某 TLV 类型 | ieee1905.tlv.type == 0x01(例:AL MAC Address TLV) |
抓包实操提醒:
- 优先在 LAN 有线侧抓;纯无线 monitor 很难稳定捕获 1905 组播控制面
- 交换机若开启过严组播抑制,可能丢掉
01:80:C2:00:00:13- 看到 1905 帧 ≠ 一定是 EasyMesh:还要看是否有
0x8000+MAP 消息或0x80+MAP TLV(见EASYMESH-01-EasyMesh入门与概念辨析.md)
3. CMDU 结构(Control Message Data Unit)—— 协议核心
CMDU 是 1905.1 的基本消息单元。结构分两段:CMDU Header + 若干 TLV。
3.1 CMDU Header 字段(协议定义)
| 字段 | 长度 | 含义 |
|---|---|---|
| messageVersion | 1B | 消息版本,目前为 0x00 |
| reservedField0 | 1B | 保留,必须为 0 |
| messageType | 2B | 消息类型,决定这条 CMDU 是什么消息(见第 4 节) |
| messageId | 2B | 消息 ID,用于去重和关联请求/响应 |
| fragmentId | 1B | 分片号,从 0 开始 |
| flags | 1B | 标志位,目前用 2 个 bit: bit7 = LastFragmentIndicator(最后分片标志) bit6 = RelayIndicator(中继标志) bit5~0 保留,必须为 0 |
关键点:
- messageType 决定一切:收到 CMDU 后,先看 messageType 再决定怎么解析后面的 TLV
- messageId + 源地址 是去重和分片重组的 key
- RelayIndicator 仅控制组播 CMDU 的中继转发行为,对单播无影响(第 7.4 节详述)
3.2 代码里的 Header 结构(佐证字段布局)
prplMesh 用 packed struct 精确还原协议字段:
// ieee1905_transport.h
#pragma pack(push, 1)
struct Ieee1905CmduHeader {
uint8_t messageVersion;
uint8_t _reservedField0;
uint16_t messageType;
uint16_t messageId;
uint8_t fragmentId;
uint8_t flags;
void SetLastFragmentIndicator(bool value) {
flags = (flags & ~


521

被折叠的 条评论
为什么被折叠?



