Skip to content

feat(net): implement AF_PACKET fanout#2143

Open
fslongjin wants to merge 6 commits into
DragonOS-Community:masterfrom
fslongjin:codex/pr-2119-review
Open

feat(net): implement AF_PACKET fanout#2143
fslongjin wants to merge 6 commits into
DragonOS-Community:masterfrom
fslongjin:codex/pr-2119-review

Conversation

@fslongjin

Copy link
Copy Markdown
Member

Closes #2032.

Supersedes #2119 with the same AF_PACKET fanout implementation rebased onto the current master, including the merged PACKET_ADD_MEMBERSHIP / PACKET_DROP_MEMBERSHIP support.

What changed

  • implement Linux-compatible PACKET_FANOUT modes, flags, group lifecycle, and control-plane semantics;
  • preserve PACKET_MR membership handling and its close-time device reference cleanup after the rebase;
  • stop inactive sockets from receiving through stale immutable topology snapshots;
  • wake the namespace poller for deferred AF_PACKET topology cleanup and retry allocation failures with bounded exponential backoff;
  • keep cleanup-only and network wake reasons separate so cleanup does not spuriously schedule interface NAPI;
  • add a regression test for closing a socket that combines PROMISC membership with fanout membership.

Validation

  • make all -j 8
  • make kernel
  • QEMU: af_packet_sockopt_test (38/38 passed)
  • QEMU: af_packet_e2e_test (14/14 passed)
  • QEMU: af_packet_mcast_test (14/14 passed)

The implementation and rebase were independently reviewed for concurrency, lifecycle safety, Linux semantics, performance, security, and regression risk.

sparkzky and others added 6 commits July 17, 2026 17:51
Implement PACKET_FANOUT (setsockopt SOL_PACKET option 18) for AF_PACKET sockets, distributing ingress frames across group members instead of broadcasting a full copy to every socket.

Supported fanout modes: HASH (flow hash), LB (round-robin), CPU, ROLLOVER, RND. Supported flags: FLAG_ROLLOVER (backup fallback), FLAG_UNIQUEID (kernel-assigned group id). Unsupported modes (QM/CBPF/EBPF) and flags (DEFRAG/IGNORE_OUTGOING) return EINVAL.

Architecture: FanoutGroup registry in NetNamespace using RCU copy-on-write (RcuArcSlot), mirroring the existing packet_sockets broadcast registry. deliver_to_packet_sockets skips fanout members in the broadcast loop and dispatches one copy per group via group.deliver(). join/leave TOCTOU closed under fanout_groups_writer lock. Per-packet deliver path is zero-allocation (two-pass member selection). AtomicBool fanout_active mirror avoids RwSem on the broadcast hot path.

Closes DragonOS-Community#2032

Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com>
…_TIMESTAMP

Fix three P1 issues found in adversarial review:

1. ABBA deadlock: join acquired fanout_groups_writer → fanout.write() while
   leave acquired fanout.write() → fanout_groups_writer. Unified lock order
   to always fanout_groups_writer → fanout.write() by moving the socket's
   fanout field clear into the netns leave path under the writer lock.

2. TOCTOU race in join_fanout: the EBUSY check (fanout.read().is_some())
   ran outside the writer lock, allowing concurrent setsockopt on the same
   socket to bypass it and leak the socket into two groups simultaneously.
   Moved the check inside fanout_group_join after acquiring the writer lock.

3. PACKET_TIMESTAMP constant (value 17) was inadvertently removed from the
   packet_option module. Restored.

Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com>
Bundle the 7 fanout join parameters (id_req, unique, mode, flags, sock_type,
bound_ifindex, bound_protocol) into a FanoutJoinParams struct, reducing
fanout_group_join's argument count from 9 to 3 and clearing the
clippy::too_many_arguments error under #![deny(clippy::all)].

Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com>
Reduce FanoutGroup::new from 6 positional args to (id, params) by reusing
the existing FanoutJoinParams bundle (a Copy struct), eliminating the
duplicated argument lists at both join call sites.

Extract publish_fanout_groups() helper for the registry Vec rebuild +
RCU snapshot publish pattern that was triplicated across join, leave,
and cleanup.

Net -20 lines; behavior unchanged.

Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com>
Rework fanout registration and delivery around one immutable per-netns RCU topology so plain sockets and fanout groups cannot observe split registry state. Make join, leave, close, and orphan cleanup construct replacement snapshots before publication and preserve allocator and membership invariants on allocation failure.

Align the data path with Linux semantics for HASH, LB, CPU, RND, and ROLLOVER modes, including per-primary rollover state, fragment-safe flow hashing, nested VLAN protocol discovery, PACKET_FANOUT_FLAG_UNIQUEID, IGNORE_OUTGOING, and the 8-byte fanout_args max_num_members ABI.

Keep protocol and flow parsing lazy and frame-local, preserve the existing cBPF/VLAN ingress path, and avoid allocations or locks in packet delivery.

Add control-plane and end-to-end coverage for option validation, group capacity, lifecycle rules, and exactly-once balanced delivery. The new LB regression sends 16 frames and requires a total of 16 deliveries split 8/8 across two sockets.

Validation: make kernel; af_packet_sockopt_test (38/38); af_packet_e2e_test (14/14).
Signed-off-by: longjin <longjin@dragonos.org>
Wake the network-namespace poller when a fallible AF_PACKET topology unregister leaves stale entries behind. Keep cleanup and network wake reasons separate so cleanup-only work does not spuriously schedule interface NAPI.

Retry failed immutable-topology rebuilds with a bounded exponential delay and an absolute deadline. Treat inactive plain and fanout members as stale immediately so closed sockets cannot receive frames while deferred cleanup is pending.

Add a regression test covering the rebased PACKET_MR_PROMISC and PACKET_FANOUT close path, including restoration of the interface promiscuity reference count.

Tested with make kernel and DragonOS QEMU dunitest suites: af_packet_sockopt_test (38/38), af_packet_e2e_test (14/14), and af_packet_mcast_test (14/14).

Signed-off-by: longjin <longjin@dragonos.org>
@github-actions github-actions Bot added the enhancement New feature or request label Jul 17, 2026
@fslongjin

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38846e2225

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +172 to +176
pub(crate) fn matches(&self, params: FanoutJoinParams) -> bool {
self.mode == params.mode
&& self.flags == params.flags
&& self.bound_ifindex == params.bound_ifindex
&& self.bound_protocol == params.bound_protocol

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 校验 fanout 成员的 socket 类型

当 SOCK_RAW socket 已创建 fanout 组后,相同协议、网卡、模式和标志的 SOCK_DGRAM socket 会被此处允许加入,因为组身份完全没有记录或比较 sock_type。Linux 6.6 要求组成员的 socket 类型一致,否则返回 EINVAL;当前实现会使同一组按目标成员随机呈现 RAW 或 DGRAM 报文布局,因此应将 PacketSocketType 纳入 FanoutJoinParamsFanoutGroup 和此匹配条件。

Useful? React with 👍 / 👎.

Comment on lines +193 to +197
let mut members = Vec::new();
members
.try_reserve_exact(self.members.len().saturating_add(1))
.map_err(|_| SystemError::ENOMEM)?;
members.extend(self.members.iter().cloned());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 避免大型 fanout 组加入时的二次复杂度

使用 8 字节 fanout_args 创建大型组时,每次加入成员都会分配新 Vec 并复制全部现有成员;构建允许的 65536 成员组累计复制约 21 亿个条目,而且旧快照在 RCU 宽限期内还可能同时存活。这使明确支持的大容量组遭遇 O(n²) 时间和内存流量,应采用结构共享或分块的不可变成员快照,避免每次加入全量复制。

Useful? React with 👍 / 👎.

// sets FLOW_DIS_IS_FRAGMENT and suppresses port extraction.
return (next, offset, false);
}
_ => return (next, offset, true),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 继续解析 IPv6 Authentication Header

当 IPv6 报文包含 Authentication Header(next-header 51)并继续承载 TCP/UDP 时,此默认分支把 AH 当作最终传输协议,导致端口始终不参与 HASH fanout。同一地址对之间的多个 AH 保护连接因此都会落到同一成员,而 Linux 流解析器会越过 AH;应按 AH 的 (payload_len + 2) * 4 长度推进并继续解析其 next-header。

Useful? React with 👍 / 👎.

.by_id
.try_reserve(1)
.map_err(|_| SystemError::ENOMEM)?;
let new_id = writer.id_alloc.alloc().ok_or(SystemError::ENOMEM)? as u16;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge 在 fanout ID 耗尽时返回 ENOSPC

当一个网络命名空间的 65536 个 fanout group ID 全部占用后,IdAllocator::alloc()None 表示 ID 空间耗尽,但这里将 PACKET_FANOUT_FLAG_UNIQUEID 请求报告为 ENOMEM。Linux 的 IDA 路径在该场景返回 ENOSPC,因此依赖 errno 区分容量耗尽与内存压力的程序会得到错误结果。

Useful? React with 👍 / 👎.

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 PACKET_FANOUT for AF_PACKET load balancing

2 participants