Skip to content

feat(packet): 实现 TPACKET V1/V2 mmap ring buffer (Issue #2030)#2096

Open
sparkzky wants to merge 8 commits into
DragonOS-Community:masterfrom
sparkzky:issue-2030-tpacket-ring
Open

feat(packet): 实现 TPACKET V1/V2 mmap ring buffer (Issue #2030)#2096
sparkzky wants to merge 8 commits into
DragonOS-Community:masterfrom
sparkzky:issue-2030-tpacket-ring

Conversation

@sparkzky

@sparkzky sparkzky commented Jul 13, 2026

Copy link
Copy Markdown
Member

概述

为 DragonOS AF_PACKET socket 实现 TPACKET mmap ring buffer(Issue #2030),使 tcpdump/libpcap 能通过零拷贝环形缓冲区高效抓包。本次实现 Phase 1:TPACKET V1/V2 的 PACKET_RX_RING + mmap + poll/epoll 唤醒 + PACKET_VERSION/STATISTICS/RESERVE/HDRLEN

Closes #2030

改动

  • uapi.rs: 补充 TPACKET 常量与 #[repr(C)] 结构体(TpacketReq/Hdr/2Hdr/Stats
  • ring.rs (新建): ring buffer 核心,per-block 物理页分配,VLAN 剥离,tp_status Release 序发布
  • base.rs: Socket trait 新增 mmap_layout() 方法
  • inode.rs: blanket impl 转发 page_cache/fs/metadata,无 ring 时 mmap() 返回 EINVAL
  • mod.rs: PacketSocket 集成 rx_ring/tpacket_version/tp_reserve 字段
  • rx.rs: can_recv 检查 ring USER 帧,deliver_from_iface ring 路径唤醒 wait_queue/epoll/fasync
  • sockopt.rs: SOL_PACKET 选项处理(VERSION/RX_RING/RESERVE/STATISTICS/HDRLEN 等)
  • packet_ring_test (新建): 用户态 C 测试程序

设计要点

  • mmap 通过 page_cache + fake fs 模型:与 perf 子系统一致,复用现有 page fault 机制
  • Socket trait 转发:参考 ioctl 转发,新增 mmap_layout() 返回 page_cache + fs + size
  • 接收路径双模式:ring 激活时写 ring 帧;未激活走现有 rx_buffer 路径,向后兼容
  • 并发安全Arc<Mutex<PacketRing>> 串行化 write_frame,外层 rx_ring Mutex 只保护 setup/teardown
  • per-block 物理页分配:每个 block 独立分配 block_size 连续物理页,避免单次大块分配在内存碎片化时 ENOMEM(对应 Linux alloc_pg_vec

编译验证

```
make kernel
Kernel Build Done.
```

测试

  • 编译通过
  • QEMU 中运行 packet_ring_test
  • tcpdump 抓包验证
  • VLAN 帧处理
  • 无 ring 时 mmap 返回 EINVAL

…nity#2030)

为 AF_PACKET socket 实现 TPACKET mmap ring buffer,使 tcpdump/libpcap 能
通过零拷贝环形缓冲区高效抓包。本次实现 Phase 1:TPACKET V1/V2 的
PACKET_RX_RING + mmap + poll/epoll 唤醒 + PACKET_VERSION/STATISTICS/RESERVE/HDRLEN。

主要改动:
- uapi.rs: 补充 TPACKET 常量与 #[repr(C)] 结构体(TpacketReq/Hdr/2Hdr/Stats)
- ring.rs: 新建 ring buffer 核心,per-block 物理页分配(避免大块连续分配在
  内存碎片化时 ENOMEM),VLAN 剥离,tp_status Release 序发布
- base.rs: Socket trait 新增 mmap_layout() 方法
- inode.rs: blanket impl 转发 page_cache/fs/metadata,mmap() 无 ring 时返回 EINVAL
- mod.rs: PacketSocket 集成 rx_ring/tpacket_version/tp_reserve 字段
- rx.rs: can_recv 检查 ring USER 帧,deliver_from_iface ring 路径唤醒
  wait_queue/epoll/fasync
- sockopt.rs: SOL_PACKET 选项处理(VERSION/RX_RING/RESERVE/STATISTICS/HDRLEN 等)
- packet_ring_test: 用户态 C 测试程序

对抗性 review 修复(P0/P1):
- ring.rs: 修复 fill_frame 中 ifindex/pkt_type 未定义编译错误
- rx.rs: ring 路径补充 send_sigio(FASYNC_POLL_IN)
- ring.rs: page_mkwrite 调用 filemap_page_mkwrite(计划 P1-1 修正)
- ring.rs: per-block 物理页分配(计划 P2-4 修正),frame_base 按 block 索引寻址

编译验证:make kernel 通过,产物 bin/kernel/kernel.elf

Signed-off-by: --global <sparkhhhhhhhhhh@outlook.com>
@github-actions github-actions Bot added the enhancement New feature or request label Jul 13, 2026
sparkzky added 7 commits July 13, 2026 19:42
P2-1: has_user_frames 加 O(N) 扫描性能注释(非热路径,可接受;
未来可优化为 AtomicU32 user_frame_count 懒更新)
P2-2: V2 时间戳改用 PosixTimeSpec::now() 获取真纳秒(等价 Linux
ktime_get_real_ts64),不再用微秒×1000 缩放;V1 保持微秒不变
P2-3: packet_ring_test 改为自包含——poll 前主动 sendto 发送测试帧
到 lo,读帧时校验数据一致性,rc=0 仅在校验通过时返回
P2-4: validate_ring_config 将 block_nr*block_size 溢出检查前移到
frame_size 校验之前(防御性编程)

编译验证:make kernel 通过

Signed-off-by: --global <sparkhhhhhhhhhh@outlook.com>
根因:PacketRing::setup 分配页时用 PageType::Normal,但
filemap_page_mkwrite 要求 PageType::File → 返回 SIGBUS,
导致用户空间写 tp_status 翻回帧时崩溃。

修复:PacketFakeFs::page_mkwrite 直接返回 empty(),与 PerfFakeFs
模式一致。ring buffer 页是预分配内核页,不需要文件回写准备。
do_wp_page 会正确跳过 Normal 页的 dirty-tracking。

测试:packet_ring_test 支持 V1/V2 命令行切换,两个版本均在
DragonOS QEMU 内端到端验证通过(11步全部 PASSED)。

同时为 filemap_page_mkwrite 的每个 SIGBUS 返回点添加 log::warn!
诊断日志,便于未来排查类似问题。

Refs: DragonOS-Community#2030
Signed-off-by: --global <sparkhhhhhhhhhh@outlook.com>
1. tp_status原子/非原子混用UB: fill_frame中先raw write tp_status=0
   再由publish()原子写入,违反Rust内存模型。删除冗余raw write,
   页在分配时已清零,publish()是唯一写入者。

2. wire_len对DGRAM错误减MAC头: tp_len应报告完整可见包长,
   MAC头跳过在数据拷贝时处理,不在tp_len中减。

3. 硬编码偏移改用struct: fill_frame从*(dst.add(N) as *mut T)=V
   改为core::ptr::write(dst as *mut TpacketHdr/Tpacket2Hdr, hdr{...}),
   编译器检查布局,消除uapi.rs中的#[allow(dead_code)]。

4. mmap bounds校验: inode.rs mmap()增加offset+len越界检查,
   超出ring size返回EINVAL(匹配Linux行为)。

5. 清理死代码: 删除不可达的frames_per_block==0检查,
   删除PacketMetadata中未使用的dst_mac/wire_len/mac_offset字段,
   删除re-export上的#[allow(unused_imports)]。

V2端到端测试验证通过,无回归。

Refs: DragonOS-Community#2030
1. cargo fmt: 格式化 ring.rs/mod.rs/rx.rs/inode.rs
2. af_packet_sockopt.cc: PACKET_VERSION 从“不支持”列表移除,
   新增独立测试验证 V1/V2 接受、非法值返回 EINVAL(匹配 Linux 6.6)
3. Dunitest 此前因 ASSERT_EQ(setsockopt(VERSION,999), 0) 失败而退出

Refs: DragonOS-Community#2030
Linus 风格 review 后的精简(PR DragonOS-Community#2096):

- fault.rs: 还原到 origin/master。PR 给共享核心缺页路径
  filemap_page_mkwrite 塞的 7 个 log::warn! 是纯调试脚手架——
  packet ring 通过 PacketFakeFs::page_mkwrite 重写已完全绕过该函数,
  这些日志污染的是 ext2 等所有真实文件系统的热路径。
- ring.rs: has_user_frames 删除 20 行描述「未实现的未来优化」的注释
  (描述不存在代码的文档比没有更糟);page_mkwrite 14 行注释压缩到 4 行;
  删除冗余的 wire_len = visible_len 别名;sll_off 复用 hdrlen() 单一
  真相源替代魔数 28/32。

编译通过,cargo fmt 干净。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(net): implement TPACKET mmap ring buffer for AF_PACKET (PACKET_RX_RING / PACKET_TX_RING)

1 participant