从 UDP 到 Raw Ethernet

基于 ns3-rdma 开源项目的 RoCE v1 版本改造全景指南

1. 架构深度剖析

理解 v2 (现有代码) 与 v1 (目标代码) 在协议栈层面的本质区别。

现状: ns3-rdma (v2)

基于 IP 路由
L4 Trans UDP Header (Port 4791)
L3 Network IP Header (ECN, DSCP)
L2 Link Ethernet (Type 0x0800 IPv4)
问题点: RoCE v1 不应该有 UDP 和 IP 头部。现有代码依赖 IP 头中的 DSCP 做流控,依赖 ECN 做拥塞控制。

目标: RoCE v1 改造版

纯以太网
Custom IB BTH Header
Custom IB GRH Header
L2 Link Ethernet (Type 0x8915)
改造核心: 移除 IP/UDP 层,手动构建 GRH/BTH,直接操作 MAC 层。

2. 改造实战指南 (大致方案,未校验)

从应用层到物理层的具体 C++ 代码修改步骤。

1

重构 Socket 通信机制

RoCE v1 不使用 IP 寻址,因此必须放弃 UdpSocketFactory,转而使用 ns-3 提供的 PacketSocketFactory 直接操作链路层。

原始代码 (v2 - udp-echo-client.cc)
// 典型的 UDP Socket
TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
m_socket = Socket::CreateSocket (GetNode (), tid);
m_socket->Bind ();
// 使用 IP 地址连接
m_socket->Connect (InetSocketAddress (m_peerAddress, m_peerPort));
修改后 (v1 - roce-client.cc)
#include "ns3/packet-socket-factory.h"

// 1. 切换到 PacketSocket
TypeId tid = TypeId::LookupByName ("ns3::PacketSocketFactory");
m_socket = Socket::CreateSocket (GetNode (), tid);

// 2. 准备物理地址与协议类型
PacketSocketAddress socketAddr;
socketAddr.SetSingleDevice (m_netDevice->GetIfIndex());
socketAddr.SetPhysicalAddress (m_destMacAddress); // 必须预先获知 MAC
socketAddr.SetProtocol (0x8915); // 关键:RoCE v1 EtherType

// 3. 绑定
m_socket->Bind (socketAddr);
m_socket->Connect (socketAddr);
2

实现 RoCE 专用头部 (GRH & BTH)

由于移除了 UDP/IP,我们需要手动实现 ns3::Header 子类来构建物理线路上的字节流。

src/internet/model/roce-grh-header.cc
class RoceGrhHeader : public Header {
    // ... 成员变量定义 ...
    uint32_t m_flowLabel; // 20 bits
    uint8_t m_tclass;     // 8 bits (重要:用于 QoS/PFC 映射)
    Ipv6Address m_sGid;   // 128 bits GID
    Ipv6Address m_dGid;
    
    // 序列化逻辑:必须符合 Big Endian 网络序
    void Serialize (Buffer::Iterator start) const override {
        Buffer::Iterator i = start;
        
        // 对应 IPv6 头部的第一个 32位字:Version(6) + TClass + FlowLabel
        // 注意位操作拼接
        uint32_t vtf = (6 << 28) | (m_tclass << 20) | (m_flowLabel & 0xFFFFF);
        i.WriteHtonU32 (vtf); 
        
        i.WriteHtonU16 (m_payloadLen);
        i.WriteU8 (m_nextHeader); // 通常指向 0x1B (BTH)
        i.WriteU8 (m_hopLimit);
        
        // 写入 128位 GID
        WriteTo (i, m_sGid);
        WriteTo (i, m_dGid);
    }
};
3

修改交换机与流控逻辑

注意: v1 极其依赖 PFC (Priority Flow Control)。必须确保交换机不再查看 IP 头的 DSCP 字段,而是查看 GRH 中的 Traffic Class。

  • 禁用 ECN:broadcom-node.cc 中,移除或注释掉所有涉及 PeekHeader<Ipv4Header> 的代码。v1 包里没有 IP 头,强行读取会导致错误。
  • 流控映射: 修改 qbb-net-device.cc 的入队逻辑。使用 Packet::PeekHeader<RoceGrhHeader> 读取 TClass,将其映射到硬件优先级队列,从而触发正确的 PAUSE 帧。

3. 协议内容与仿真代码映射表

速查表:RoCE v1 协议规范中的字段在 C++ 代码中是如何实现的。

协议层级 协议字段 (RoCE v1) ns-3 代码实现 (C++) 作用说明
链路层 (L2) EtherType SetProtocol(0x8915) 核心标识。指示 NetDevice 封装帧时写入 0x8915。
链路层 (L2) Dest MAC SetPhysicalAddress(mac) 替代 ARP,直接指定物理地址。
网络层 (GRH) Traffic Class RoceGrhHeader::m_tclass 流控关键。决定进入交换机的哪个优先级队列,触发 PFC。
网络层 (GRH) GID Ipv6Address 类型 仅用于应用层连接验证,不用于路由。
传输层 (BTH) Dest QP RoceBthHeader::m_destQp 接收端根据此 ID 将数据送入正确的内存区域。
流控机制 PAUSE Frame qbb-net-device::SendPfc() 802.1Qbb 标准实现,发送 EtherType 0x8808 控制帧。