Skip to content

Commit 54158d1

Browse files
committed
Add Security Groups support for eBPF-based firewall filtering
Implements stateful firewall rules (Security Groups) for mvirt-ebpf: - Database schema for security groups, rules, and NIC associations - eBPF packet filtering with connection tracking (IPv4 + IPv6) - Default behavior: ingress=DENY, egress=ALLOW - Allow-only rules (no deny rules, like AWS Security Groups) - gRPC API for security group CRUD operations - Audit logging for security group events - Connection tracking cleanup task - TCP/UDP packet builders for integration tests - 20 new security group tests Architecture: - Security rules checked in tc_egress (default allow) and tc_ingress (default deny) - Connection tracking allows return traffic for established connections - CIDR matching for both IPv4 and IPv6 rules
1 parent 7472b7b commit 54158d1

16 files changed

Lines changed: 3904 additions & 49 deletions

File tree

mvirt-ebpf-programs/src/lib.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,198 @@ pub const ICMPV6_ROUTER_SOLICITATION: u8 = 133;
7979
pub const ICMPV6_ROUTER_ADVERTISEMENT: u8 = 134;
8080
pub const ICMPV6_NEIGHBOR_SOLICITATION: u8 = 135;
8181
pub const ICMPV6_NEIGHBOR_ADVERTISEMENT: u8 = 136;
82+
83+
// IP protocols for security rules
84+
pub const IPPROTO_ICMP: u8 = 1;
85+
pub const IPPROTO_TCP: u8 = 6;
86+
87+
// Security rule directions
88+
pub const DIRECTION_INGRESS: u8 = 0;
89+
pub const DIRECTION_EGRESS: u8 = 1;
90+
91+
// Security rule protocols (0 = all)
92+
pub const PROTO_ALL: u8 = 0;
93+
// IPPROTO_ICMP = 1 (already defined above)
94+
// IPPROTO_TCP = 6 (already defined above)
95+
// IPPROTO_UDP = 17 (already defined above)
96+
// IPPROTO_ICMPV6 = 58 (already defined above)
97+
98+
// Connection tracking states
99+
pub const CT_STATE_NEW: u8 = 0;
100+
pub const CT_STATE_ESTABLISHED: u8 = 1;
101+
pub const CT_STATE_RELATED: u8 = 2;
102+
103+
// Connection tracking flags
104+
pub const CT_FLAG_SEEN_REPLY: u8 = 1;
105+
pub const CT_FLAG_ASSURED: u8 = 2;
106+
107+
/// Security rule for packet filtering
108+
/// Rules are always "allow" - if ANY rule matches, packet is allowed
109+
#[repr(C)]
110+
#[derive(Clone, Copy)]
111+
pub struct SecurityRule {
112+
/// Rule is active (1) or disabled (0)
113+
pub enabled: u8,
114+
/// Direction: 0=ingress (to VM), 1=egress (from VM)
115+
pub direction: u8,
116+
/// IP protocol: 0=all, 1=ICMP, 6=TCP, 17=UDP, 58=ICMPv6
117+
pub protocol: u8,
118+
/// IP version: 4=IPv4, 6=IPv6, 0=both
119+
pub ip_version: u8,
120+
/// Start of port range (inclusive), 0 = any
121+
pub port_start: u16,
122+
/// End of port range (inclusive), 0 = any
123+
pub port_end: u16,
124+
/// CIDR network address (IPv4 in first 4 bytes, IPv6 uses all 16)
125+
pub cidr_addr: [u8; 16],
126+
/// CIDR prefix length (0-32 for IPv4, 0-128 for IPv6)
127+
pub cidr_prefix_len: u8,
128+
_padding: [u8; 3],
129+
}
130+
131+
impl SecurityRule {
132+
pub const fn new() -> Self {
133+
Self {
134+
enabled: 0,
135+
direction: 0,
136+
protocol: 0,
137+
ip_version: 0,
138+
port_start: 0,
139+
port_end: 0,
140+
cidr_addr: [0; 16],
141+
cidr_prefix_len: 0,
142+
_padding: [0; 3],
143+
}
144+
}
145+
}
146+
147+
/// Connection tracking key (5-tuple + ip version)
148+
#[repr(C)]
149+
#[derive(Clone, Copy)]
150+
pub struct ConnTrackKey {
151+
/// Source IP address (IPv4: first 4 bytes, IPv6: all 16)
152+
pub src_addr: [u8; 16],
153+
/// Destination IP address (IPv4: first 4 bytes, IPv6: all 16)
154+
pub dst_addr: [u8; 16],
155+
/// Source port (0 for ICMP)
156+
pub src_port: u16,
157+
/// Destination port (0 for ICMP, but stores ICMP id)
158+
pub dst_port: u16,
159+
/// IP protocol: 1=ICMP, 6=TCP, 17=UDP, 58=ICMPv6
160+
pub protocol: u8,
161+
/// IP version: 4 or 6
162+
pub ip_version: u8,
163+
/// Padding for alignment
164+
pub _pad: [u8; 2],
165+
}
166+
167+
impl ConnTrackKey {
168+
pub const fn new() -> Self {
169+
Self {
170+
src_addr: [0; 16],
171+
dst_addr: [0; 16],
172+
src_port: 0,
173+
dst_port: 0,
174+
protocol: 0,
175+
ip_version: 0,
176+
_pad: [0; 2],
177+
}
178+
}
179+
180+
/// Create reverse key for reply traffic lookup
181+
pub const fn reverse(&self) -> Self {
182+
Self {
183+
src_addr: self.dst_addr,
184+
dst_addr: self.src_addr,
185+
src_port: self.dst_port,
186+
dst_port: self.src_port,
187+
protocol: self.protocol,
188+
ip_version: self.ip_version,
189+
_pad: [0; 2],
190+
}
191+
}
192+
193+
/// Create a new key with all fields specified
194+
pub const fn from_tuple(
195+
src_addr: [u8; 16],
196+
dst_addr: [u8; 16],
197+
src_port: u16,
198+
dst_port: u16,
199+
protocol: u8,
200+
ip_version: u8,
201+
) -> Self {
202+
Self {
203+
src_addr,
204+
dst_addr,
205+
src_port,
206+
dst_port,
207+
protocol,
208+
ip_version,
209+
_pad: [0; 2],
210+
}
211+
}
212+
}
213+
214+
/// Connection tracking entry
215+
#[repr(C)]
216+
#[derive(Clone, Copy)]
217+
pub struct ConnTrackEntry {
218+
/// Connection state (CT_STATE_*)
219+
pub state: u8,
220+
/// Flags (CT_FLAG_*)
221+
pub flags: u8,
222+
/// Padding for alignment
223+
pub _pad: [u8; 2],
224+
/// Timestamp of last seen packet (nanoseconds since boot)
225+
pub last_seen_ns: u64,
226+
/// Total packet count for this connection
227+
pub packet_count: u64,
228+
}
229+
230+
impl ConnTrackEntry {
231+
pub const fn new() -> Self {
232+
Self {
233+
state: CT_STATE_NEW,
234+
flags: 0,
235+
_pad: [0; 2],
236+
last_seen_ns: 0,
237+
packet_count: 0,
238+
}
239+
}
240+
241+
/// Create a new entry with specified state and timestamp
242+
pub const fn with_state(state: u8, last_seen_ns: u64) -> Self {
243+
Self {
244+
state,
245+
flags: 0,
246+
_pad: [0; 2],
247+
last_seen_ns,
248+
packet_count: 1,
249+
}
250+
}
251+
}
252+
253+
/// NIC security configuration
254+
/// Maps NIC ifindex to its security settings
255+
#[repr(C)]
256+
#[derive(Clone, Copy)]
257+
pub struct NicSecurityConfig {
258+
/// Whether security filtering is enabled for this NIC
259+
pub enabled: u8,
260+
_padding: [u8; 3],
261+
/// Start index into SECURITY_RULES map for this NIC's rules
262+
pub rules_start: u32,
263+
/// Number of rules for this NIC
264+
pub rules_count: u32,
265+
}
266+
267+
impl NicSecurityConfig {
268+
pub const fn new() -> Self {
269+
Self {
270+
enabled: 0,
271+
_padding: [0; 3],
272+
rules_start: 0,
273+
rules_count: 0,
274+
}
275+
}
276+
}

0 commit comments

Comments
 (0)