EASYMESH-02-ieee1905-1-协议详解

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 跨口转发

为什么要这条规则——分清两种"转发":

  1. Linux bridge 的泛洪转发(要禁止):bridge 收到组播包默认从所有其他口发出去,是无脑复制,不可控。
  2. 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 份,干净受控 ✓

两个易错细节澄清

  1. 不是"直接丢给内核协议栈":bridge DROP 掉 FORWARD 链后,包并没有被完全丢弃,而是仍然会上交本机(本机接收路径)。本机的 1905.1 协议层(prplMesh 里是用户态进程)收到后再决定怎么处理。
  2. "不转发到其他口"是对的,但协议层可能会自己再发: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 层,直接跑在数据链路层

  1. 目标地址用 MAC
    • 广播/发现:固定组播 MAC 01:80:C2:00:00:13
    • 单播回复:直接填对端硬件/接口 MAC 或 AL MAC(从收到报文的源地址学习),不需要 ARP
  2. EtherType = 0x893A
    • 网卡/协议栈看到这个类型,交给 1905 协议栈处理,不送到 IP 协议栈
  3. 不需要 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 == 0x893aieee1905
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)

抓包实操提醒:

  1. 优先在 LAN 有线侧抓;纯无线 monitor 很难稳定捕获 1905 组播控制面
  2. 交换机若开启过严组播抑制,可能丢掉 01:80:C2:00:00:13
  3. 看到 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 & ~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值