Skip to content

Commit 23247da

Browse files
sparkzkyfslongjin
andauthored
feat(net): implement BPF filter (SO_ATTACH_FILTER) for AF_PACKET (#2031) (#2110)
* feat(net): implement BPF filter support for AF_PACKET (#2031) Extract cBPF interpreter from seccomp.rs to bpf/classic.rs as shared infrastructure. Generalize to accept &[u8] input for packet filtering. Support: BPF_W/BPF_H/BPF_B load widths, BPF_ABS/BPF_IND addressing, BPF_MSH (IP header length extraction), full ALU/JMP/RET/MISC ops. Security: validate_cbpf checks program bounds (<=4096 insns), last insn is RET, checked_add on jump targets, static div/mod k!=0 reject, ST/STX k<16, LD/LDX mode whitelist. Runtime: OOB reads return a=0, div/mod by zero returns a=0, fall-through returns 0 (drop). AF_PACKET: SO_ATTACH_FILTER/SO_DETACH_FILTER/SO_LOCK_FILTER via PSOL::SOCKET dispatch. Filter stored in RcuArcSlot (lock-free read), AtomicBool fast-path skip when no filter attached. Filter runs on frame[start..] (RAW: full frame, DGRAM: L3 payload). Seccomp: refactored to use bpf::classic, seccomp-specific validation layered on top of validate_cbpf. Big-endian serialization preserves existing behavior. Refs: #2031, #691 Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com> * test: update af_packet_sockopt dunitest for SO_ATTACH_FILTER The test AttachFilterIsNotSilentlyAccepted expected ENOPROTOOPT (option unimplemented). Now that SO_ATTACH_FILTER is implemented (PR #2110), update to expect success. Add DetachFilterReturnsEnoentWhenNoFilter test. Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com> * fix: correct TestSockFprog casing in dunitest (FProg→Fprog) Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com> * fix: pass dummy value to SO_DETACH_FILTER in dunitest sys_setsockopt with optlen=0 may short-circuit before calling set_option. Pass a dummy int to ensure the kernel handler runs. Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com> * fix(net): align AF_PACKET classic BPF with Linux Harden the shared classic BPF validator and interpreter with exact opcode validation, definite scratch-memory initialization, fail-closed packet loads, and fallible user-controlled allocations. Keep seccomp on its Linux-compatible instruction subset without serializing seccomp_data for every filter. Use a zero-copy packet view for AF_PACKET filtering, propagate real ingress metadata, implement the Linux ancillary and payload-offset semantics, and apply filter snaplen consistently to queue accounting and recv behavior. Linearize filter attach, detach, and lock operations around one control lock and an optional RCU publication point. Preserve Linux optlen, errno, lock ordering, and ENOMEM behavior. Extend AF_PACKET, seccomp, and RCU dunitests to cover validator boundaries, runtime failures, snaplen, ancillary offsets, socket option ABI behavior, and optional RCU slot lifetimes. Signed-off-by: longjin <longjin@dragonos.org> * fix(net): preserve outgoing inline VLAN filters Keep inline 802.1Q and 802.1ad headers visible to AF_PACKET SOCK_RAW filters for PACKET_OUTGOING frames, matching Linux dev_queue_xmit_nit semantics. Continue normalizing received VLAN headers into metadata, while deriving protocol, network-relative loads, RAW/DGRAM offsets, ancillary values, and queued bytes from the same packet view. Add a deterministic veth regression that filters outgoing traffic by packet type and the inline VLAN EtherType. Signed-off-by: longjin <longjin@dragonos.org> * fix(net): align classic filter socket option ABI Signed-off-by: longjin <longjin@dragonos.org> * fix(net): retain zero-length packet datagrams Signed-off-by: longjin <longjin@dragonos.org> * fix(net): harden socket filter option handling Signed-off-by: longjin <longjin@dragonos.org> * fix(rcu): harden arc slot lifetime handling Avoid entering an RCU read-side section for empty optional slots while reloading and pinning non-empty snapshots under RCU protection. Make raw slot swaps unsafe so callers must preserve removed references through a grace period, and update packet filters and signal handlers to document that obligation. Add bounded cross-task RCU lifecycle and overlap coverage plus AF_PACKET replacement state regression tests. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com> Signed-off-by: longjin <longjin@dragonos.org> Co-authored-by: longjin <longjin@dragonos.org>
1 parent 6b693da commit 23247da

19 files changed

Lines changed: 2442 additions & 470 deletions

File tree

kernel/src/bpf/classic.rs

Lines changed: 572 additions & 0 deletions
Large diffs are not rendered by default.

kernel/src/bpf/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod classic;
12
pub mod helper;
23
pub mod map;
34
pub mod prog;

kernel/src/driver/net/e1000e/e1000e_driver.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,7 @@ impl phy::RxToken for E1000ERxToken {
9090
// 向注册的 packet socket 分发数据包
9191
if let Some(iface) = self.driver.iface() {
9292
let pkt_type = crate::net::socket::packet::classify_packet(packet, &iface);
93-
if let Some(netns) = iface.net_namespace() {
94-
netns.deliver_to_packet_sockets(iface.nic_id() as u32, packet, pkt_type);
95-
}
93+
crate::net::socket::packet::deliver_to_packet_sockets(&iface, packet, pkt_type);
9694
}
9795

9896
let result = f(packet);
@@ -313,7 +311,7 @@ impl E1000EInterface {
313311
driver,
314312
common: IfaceCommon::new(
315313
iface_id,
316-
crate::driver::net::types::InterfaceType::EETHER,
314+
crate::driver::net::types::InterfaceType::ETHER,
317315
name,
318316
mtu,
319317
flags,

kernel/src/driver/net/veth.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,7 @@ impl RxToken for VethRxToken {
301301
// 向注册的 packet socket 分发数据包
302302
if let Some(iface) = self.driver.iface() {
303303
let pkt_type = crate::net::socket::packet::classify_packet(packet, &iface);
304-
if let Some(netns) = iface.net_namespace() {
305-
netns.deliver_to_packet_sockets(iface.nic_id() as u32, packet, pkt_type);
306-
}
304+
crate::net::socket::packet::deliver_to_packet_sockets(&iface, packet, pkt_type);
307305
}
308306

309307
f(packet)
@@ -405,7 +403,7 @@ impl VethInterface {
405403
driver: driver.clone(),
406404
common: IfaceCommon::new(
407405
iface_id,
408-
super::types::InterfaceType::EETHER,
406+
super::types::InterfaceType::ETHER,
409407
driver.name(),
410408
mtu,
411409
flags,

kernel/src/driver/net/virtio_net.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ impl VirtioInterface {
444444
locked_kobj_state: LockedKObjectState::default(),
445445
iface_common: super::IfaceCommon::new(
446446
iface_id,
447-
crate::driver::net::types::InterfaceType::EETHER,
447+
crate::driver::net::types::InterfaceType::ETHER,
448448
iface_name,
449449
mtu,
450450
flags,
@@ -742,9 +742,7 @@ impl phy::RxToken for VirtioNetToken {
742742
// 向注册的 packet socket 分发数据包
743743
if let Some(iface) = self.driver.iface() {
744744
let pkt_type = crate::net::socket::packet::classify_packet(packet, &iface);
745-
if let Some(netns) = iface.net_namespace() {
746-
netns.deliver_to_packet_sockets(iface.nic_id() as u32, packet, pkt_type);
747-
}
745+
crate::net::socket::packet::deliver_to_packet_sockets(&iface, packet, pkt_type);
748746
}
749747

750748
let result = f(packet);

kernel/src/net/socket/packet/binding.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::driver::net::Iface;
66
use crate::filesystem::epoll::EPollEventType;
77
use crate::net::socket::endpoint::{Endpoint, LinkLayerEndpoint};
88

9-
use super::{PacketSocket, PacketType};
9+
use super::{PacketIngressMetadata, PacketSocket};
1010

1111
/// Lock-free, coherent `(ifindex, protocol)` receive-filter snapshot.
1212
#[derive(Debug)]
@@ -91,7 +91,7 @@ impl PacketSocket {
9191
ll.protocol = protocol;
9292
if let Some(iface) = self.bound_iface.read().as_ref() {
9393
ll.addr[..6].copy_from_slice(iface.mac().as_bytes());
94-
ll.hatype = 1;
94+
ll.hatype = iface.type_() as u16;
9595
ll.halen = 6;
9696
}
9797
Ok(Endpoint::LinkLayer(ll))
@@ -108,7 +108,7 @@ impl PacketSocket {
108108
}
109109

110110
/// Registry delivery entry: the ingress interface is authoritative metadata.
111-
pub(crate) fn deliver(&self, ingress_ifindex: u32, frame: &[u8], pkt_type: PacketType) {
112-
self.deliver_from_iface(ingress_ifindex, frame, pkt_type);
111+
pub(crate) fn deliver(&self, ingress: PacketIngressMetadata, frame: &[u8]) {
112+
self.deliver_from_iface(ingress, frame);
113113
}
114114
}

kernel/src/net/socket/packet/mod.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use alloc::vec::Vec;
1212
use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
1313
use system_error::SystemError;
1414

15+
use crate::bpf::classic::SockFilter;
1516
use crate::driver::net::Iface;
1617
use crate::filesystem::epoll::EPollEventType;
1718
use crate::filesystem::vfs::{fasync::FAsyncItems, vcore::generate_inode_id, InodeId};
@@ -22,6 +23,7 @@ use crate::net::socket::{Socket, PMSG, PSOCK, PSOL};
2223
use crate::process::cred::CAPFlags;
2324
use crate::process::namespace::net_namespace::NetNamespace;
2425
use crate::process::ProcessManager;
26+
use crate::rcu::RcuOptionArcSlot;
2527

2628
#[allow(unused_imports)]
2729
pub use uapi::{
@@ -39,13 +41,35 @@ pub enum PacketSocketType {
3941
Dgram,
4042
}
4143

44+
/// Metadata captured at the packet-tap boundary. Keeping this structure
45+
/// `Copy` lets the NAPI receive path pass device information to every packet
46+
/// socket without consulting the sleepable netdevice registry.
47+
#[derive(Debug, Clone, Copy)]
48+
pub(crate) struct PacketIngressMetadata {
49+
pub ifindex: u32,
50+
pub hatype: u16,
51+
pub pkt_type: PacketType,
52+
}
53+
54+
impl PacketIngressMetadata {
55+
#[inline]
56+
fn from_iface(iface: &Arc<dyn Iface>, pkt_type: PacketType) -> Self {
57+
Self {
58+
ifindex: iface.nic_id() as u32,
59+
hatype: iface.type_() as u16,
60+
pkt_type,
61+
}
62+
}
63+
}
64+
4265
#[derive(Debug, Clone, Default)]
4366
pub struct PacketMetadata {
4467
pub src_mac: [u8; 6],
4568
#[allow(dead_code)]
4669
pub dst_mac: [u8; 6],
4770
pub protocol: u16,
4871
pub ifindex: u32,
72+
pub hatype: u16,
4973
pub pkt_type: PacketType,
5074
pub wire_len: usize,
5175
#[allow(dead_code)]
@@ -89,6 +113,10 @@ pub struct PacketSocket {
89113
open_files: AtomicUsize,
90114
pub(super) self_ref: Weak<Self>,
91115
pub(super) netns: Arc<NetNamespace>,
116+
/// cBPF filter 程序;空指针是“未安装”的唯一事实来源。
117+
pub(super) filter: RcuOptionArcSlot<Vec<SockFilter>>,
118+
/// 串行化 SO_ATTACH_FILTER、SO_DETACH_FILTER 与 SO_LOCK_FILTER。
119+
pub(super) filter_locked: Mutex<bool>,
92120
epoll_items: EPollItems,
93121
fasync_items: FAsyncItems,
94122
}
@@ -128,6 +156,8 @@ impl PacketSocket {
128156
self_ref: me.clone(),
129157
netns,
130158
epoll_items: EPollItems::default(),
159+
filter: RcuOptionArcSlot::new_none(),
160+
filter_locked: Mutex::new(false),
131161
fasync_items: FAsyncItems::default(),
132162
});
133163
socket.netns.register_packet_socket(socket.self_ref.clone());
@@ -173,7 +203,7 @@ pub fn classify_packet(frame: &[u8], iface: &Arc<dyn Iface>) -> PacketType {
173203
/// Common AF_PACKET tap for Ethernet netdevices.
174204
pub fn deliver_to_packet_sockets(iface: &Arc<dyn Iface>, frame: &[u8], pkt_type: PacketType) {
175205
if let Some(netns) = iface.net_namespace() {
176-
netns.deliver_to_packet_sockets(iface.nic_id() as u32, frame, pkt_type);
206+
netns.deliver_to_packet_sockets(PacketIngressMetadata::from_iface(iface, pkt_type), frame);
177207
}
178208
}
179209

0 commit comments

Comments
 (0)