summaryrefslogtreecommitdiff
path: root/packet-detector-ebpf
diff options
context:
space:
mode:
Diffstat (limited to 'packet-detector-ebpf')
-rw-r--r--packet-detector-ebpf/Cargo.toml12
-rw-r--r--packet-detector-ebpf/src/main.rs170
2 files changed, 182 insertions, 0 deletions
diff --git a/packet-detector-ebpf/Cargo.toml b/packet-detector-ebpf/Cargo.toml
new file mode 100644
index 0000000..7241610
--- /dev/null
+++ b/packet-detector-ebpf/Cargo.toml
@@ -0,0 +1,12 @@
1[package]
2name = "packet-detector-ebpf"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
7aya-ebpf = { workspace = true }
8network-types = "0.1.0"
9
10[[bin]]
11name = "packet-detector"
12path = "src/main.rs"
diff --git a/packet-detector-ebpf/src/main.rs b/packet-detector-ebpf/src/main.rs
new file mode 100644
index 0000000..db9e410
--- /dev/null
+++ b/packet-detector-ebpf/src/main.rs
@@ -0,0 +1,170 @@
1#![no_std]
2#![no_main]
3
4use aya_ebpf::{bindings::xdp_action, macros::{map, xdp}, maps::{HashMap, RingBuf}, programs::XdpContext};
5use network_types::{eth::{EthHdr, EtherType}, ip::{Ipv4Hdr, IpProto}, tcp::TcpHdr, udp::UdpHdr};
6
7#[repr(C)]
8#[derive(Clone, Copy)]
9pub struct ConnKey {
10 pub port_lo: u16, // lower port (client ephemeral)
11 pub port_hi: u16, // higher port (server, e.g. 8443)
12}
13
14#[repr(C)]
15pub struct TlsEvent {
16 pub src_ip: u32,
17 pub dst_ip: u32,
18 pub src_port: u16,
19 pub dst_port: u16,
20 pub tls_len: u16,
21 pub _pad: u16,
22}
23
24#[map]
25static TLS_EVENTS: RingBuf = RingBuf::with_byte_size(256 * 1024, 0);
26
27#[map]
28static ALLOWED_CONNS: HashMap<ConnKey, u8> = HashMap::with_max_entries(4096, 0);
29
30#[map]
31static BLOCKED_CONNS: HashMap<ConnKey, u8> = HashMap::with_max_entries(4096, 0);
32
33// Magic word: "hell0123" (8 bytes)
34const MAGIC: [u8; 8] = *b"hell0123";
35
36#[xdp]
37pub fn packet_detector(ctx: XdpContext) -> u32 {
38 match try_process(&ctx) {
39 Ok(action) => action,
40 Err(_) => xdp_action::XDP_PASS
41 }
42}
43
44fn try_process(ctx: &XdpContext) -> Result<u32, ()> {
45 let eth = ptr::<EthHdr>(ctx, 0)?;
46 if unsafe { (*eth).ether_type } != EtherType::Ipv4.into() { return Ok(xdp_action::XDP_PASS); }
47
48 let ip = ptr::<Ipv4Hdr>(ctx, EthHdr::LEN)?;
49 let src_ip = u32::from_be_bytes(unsafe { (*ip).src_addr });
50 let ip_hdr_len = (unsafe { (*ip).vihl } & 0x0F) as usize * 4;
51
52 match unsafe { (*ip).proto } {
53 IpProto::Tcp => process_tcp(ctx, ip, ip_hdr_len, src_ip),
54 IpProto::Udp => process_udp(ctx, ip, ip_hdr_len, src_ip),
55 _ => Ok(xdp_action::XDP_PASS),
56 }
57}
58
59fn make_conn_key(src_port: u16, dst_port: u16) -> ConnKey {
60 if src_port < dst_port {
61 ConnKey { port_lo: src_port, port_hi: dst_port }
62 } else {
63 ConnKey { port_lo: dst_port, port_hi: src_port }
64 }
65}
66
67fn process_tcp(ctx: &XdpContext, ip: *const Ipv4Hdr, ip_hdr_len: usize, src_ip: u32) -> Result<u32, ()> {
68 let tcp = ptr::<TcpHdr>(ctx, EthHdr::LEN + ip_hdr_len)?;
69 let payload_off = EthHdr::LEN + ip_hdr_len + (unsafe { (*tcp).doff() } as usize) * 4;
70
71 let src_port = u16::from_be_bytes(unsafe { (*tcp).source });
72 let dst_port = u16::from_be_bytes(unsafe { (*tcp).dest });
73
74 // Only filter TLS ports (443/8443)
75 let is_tls_port = dst_port == 443 || src_port == 443 || dst_port == 8443 || src_port == 8443;
76 if !is_tls_port {
77 return Ok(xdp_action::XDP_PASS);
78 }
79
80 let conn_key = make_conn_key(src_port, dst_port);
81
82 // Explicitly blocked (invalid cert) - drop
83 if unsafe { BLOCKED_CONNS.get(&conn_key) }.is_some() {
84 return Ok(xdp_action::XDP_DROP);
85 }
86
87 // Already validated (valid cert) - allow
88 if unsafe { ALLOWED_CONNS.get(&conn_key) }.is_some() {
89 return Ok(xdp_action::XDP_PASS);
90 }
91
92 // Check if this packet has a TLS cert - send to userspace for validation
93 if is_tls_cert(ctx, payload_off) {
94 const SZ: usize = 512;
95 if let Some(tls_ptr) = bounds(ctx, payload_off, SZ) {
96 if let Some(mut entry) = TLS_EVENTS.reserve::<[u8; 16 + SZ]>(0) {
97 let p = entry.as_mut_ptr() as *mut u8;
98 unsafe {
99 core::ptr::write(p as *mut TlsEvent, TlsEvent {
100 src_ip,
101 dst_ip: u32::from_be_bytes((*ip).dst_addr),
102 src_port,
103 dst_port,
104 tls_len: SZ as u16,
105 _pad: 0,
106 });
107 for i in 0..SZ { *p.add(16 + i) = *tls_ptr.add(i); }
108 }
109 entry.submit(0);
110 }
111 }
112 }
113
114 // Allow packet through - handshake continues, userspace will decide later
115 Ok(xdp_action::XDP_PASS)
116}
117
118fn process_udp(ctx: &XdpContext, ip: *const Ipv4Hdr, ip_hdr_len: usize, src_ip: u32) -> Result<u32, ()> {
119 let udp = ptr::<UdpHdr>(ctx, EthHdr::LEN + ip_hdr_len)?;
120 let payload_off = EthHdr::LEN + ip_hdr_len + UdpHdr::LEN;
121
122 // Check for magic word at payload start
123 if let Some(data) = bounds(ctx, payload_off, MAGIC.len()) {
124 let mut matched = true;
125 for i in 0..MAGIC.len() {
126 if unsafe { *data.add(i) } != MAGIC[i] { matched = false; break; }
127 }
128 if matched {
129 // Send event to userspace (reuse TlsEvent struct)
130 if let Some(mut entry) = TLS_EVENTS.reserve::<[u8; 16]>(0) {
131 let p = entry.as_mut_ptr() as *mut u8;
132 unsafe {
133 core::ptr::write(p as *mut TlsEvent, TlsEvent {
134 src_ip,
135 dst_ip: u32::from_be_bytes((*ip).dst_addr),
136 src_port: u16::from_be_bytes((*udp).src),
137 dst_port: u16::from_be_bytes((*udp).dst),
138 tls_len: 0, // 0 = UDP magic match
139 _pad: 0,
140 });
141 }
142 entry.submit(0);
143 }
144 return Ok(xdp_action::XDP_PASS);
145 }
146 // No match - drop the packet
147 return Ok(xdp_action::XDP_DROP);
148 }
149 Ok(xdp_action::XDP_PASS)
150}
151
152#[inline(always)]
153fn is_tls_cert(ctx: &XdpContext, off: usize) -> bool {
154 if let Some(p) = bounds(ctx, off, 6) {
155 unsafe { *p == 0x16 && *p.add(1) == 0x03 && *p.add(2) <= 0x03 && *p.add(5) == 0x0B }
156 } else { false }
157}
158
159#[inline(always)]
160fn bounds(ctx: &XdpContext, off: usize, len: usize) -> Option<*const u8> {
161 if ctx.data() + off + len > ctx.data_end() { None } else { Some((ctx.data() + off) as *const u8) }
162}
163
164#[inline(always)]
165fn ptr<T>(ctx: &XdpContext, off: usize) -> Result<*const T, ()> {
166 bounds(ctx, off, core::mem::size_of::<T>()).map(|p| p as *const T).ok_or(())
167}
168
169#[panic_handler]
170fn panic(_: &core::panic::PanicInfo) -> ! { unsafe { core::hint::unreachable_unchecked() } }