mirror of
https://github.com/xzcrpw/blackwall.git
synced 2026-05-03 13:12:36 +02:00
v2.0.0: adaptive eBPF firewall with AI honeypot and P2P threat mesh
This commit is contained in:
commit
37c6bbf5a1
133 changed files with 28073 additions and 0 deletions
220
tarpit/src/protocols/dns.rs
Executable file
220
tarpit/src/protocols/dns.rs
Executable file
|
|
@ -0,0 +1,220 @@
|
|||
//! DNS canary honeypot.
|
||||
//!
|
||||
//! Listens on UDP port 53, responds to all queries with a configurable canary IP,
|
||||
//! and logs attacker DNS queries for forensic analysis.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::net::Ipv4Addr;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
/// Canary IP to return in A record responses.
|
||||
const DEFAULT_CANARY_IP: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 200);
|
||||
|
||||
/// Maximum DNS message size we handle.
|
||||
const MAX_DNS_MSG: usize = 512;
|
||||
|
||||
/// Run a DNS canary server on the specified bind address.
|
||||
/// Responds to all A queries with the canary IP.
|
||||
pub async fn run_dns_canary(bind_addr: &str, canary_ip: Ipv4Addr) -> anyhow::Result<()> {
|
||||
let socket = UdpSocket::bind(bind_addr).await?;
|
||||
tracing::info!(addr = %bind_addr, canary = %canary_ip, "DNS canary listening");
|
||||
|
||||
let mut buf = [0u8; MAX_DNS_MSG];
|
||||
loop {
|
||||
let (len, src) = socket.recv_from(&mut buf).await?;
|
||||
if len < 12 {
|
||||
continue; // Too short for DNS header
|
||||
}
|
||||
|
||||
let query = &buf[..len];
|
||||
let qname = extract_qname(query);
|
||||
tracing::info!(
|
||||
attacker = %src,
|
||||
query = %qname,
|
||||
"DNS canary query"
|
||||
);
|
||||
|
||||
if let Some(response) = build_response(query, canary_ip) {
|
||||
let _ = socket.send_to(&response, src).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the query name from a DNS message (after the 12-byte header).
|
||||
fn extract_qname(msg: &[u8]) -> String {
|
||||
if msg.len() < 13 {
|
||||
return String::from("<empty>");
|
||||
}
|
||||
|
||||
let mut name = String::new();
|
||||
let mut pos = 12;
|
||||
let mut first = true;
|
||||
|
||||
for _ in 0..128 {
|
||||
if pos >= msg.len() {
|
||||
break;
|
||||
}
|
||||
let label_len = msg[pos] as usize;
|
||||
if label_len == 0 {
|
||||
break;
|
||||
}
|
||||
if !first {
|
||||
name.push('.');
|
||||
}
|
||||
first = false;
|
||||
pos += 1;
|
||||
let end = pos + label_len;
|
||||
if end > msg.len() {
|
||||
break;
|
||||
}
|
||||
for &b in &msg[pos..end] {
|
||||
if b.is_ascii_graphic() || b == b'-' || b == b'_' {
|
||||
name.push(b as char);
|
||||
} else {
|
||||
name.push('?');
|
||||
}
|
||||
}
|
||||
pos = end;
|
||||
}
|
||||
|
||||
if name.is_empty() {
|
||||
String::from("<root>")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a DNS response with a single A record pointing to the canary IP.
|
||||
fn build_response(query: &[u8], canary_ip: Ipv4Addr) -> Option<Vec<u8>> {
|
||||
if query.len() < 12 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut resp = Vec::with_capacity(query.len() + 16);
|
||||
|
||||
// Copy transaction ID from query
|
||||
resp.push(query[0]);
|
||||
resp.push(query[1]);
|
||||
|
||||
// Flags: standard response, recursion available, no error
|
||||
resp.push(0x81); // QR=1, opcode=0, AA=0, TC=0, RD=1
|
||||
resp.push(0x80); // RA=1, Z=0, RCODE=0
|
||||
|
||||
// QDCOUNT = 1 (echo the question)
|
||||
resp.push(0x00);
|
||||
resp.push(0x01);
|
||||
// ANCOUNT = 1 (one answer)
|
||||
resp.push(0x00);
|
||||
resp.push(0x01);
|
||||
// NSCOUNT = 0
|
||||
resp.push(0x00);
|
||||
resp.push(0x00);
|
||||
// ARCOUNT = 0
|
||||
resp.push(0x00);
|
||||
resp.push(0x00);
|
||||
|
||||
// Copy the question section from query
|
||||
let question_start = 12;
|
||||
let mut pos = question_start;
|
||||
// Walk through the question name
|
||||
for _ in 0..128 {
|
||||
if pos >= query.len() {
|
||||
return None;
|
||||
}
|
||||
let label_len = query[pos] as usize;
|
||||
if label_len == 0 {
|
||||
pos += 1; // Skip the zero terminator
|
||||
break;
|
||||
}
|
||||
pos += 1 + label_len;
|
||||
}
|
||||
// Skip QTYPE (2) + QCLASS (2)
|
||||
if pos + 4 > query.len() {
|
||||
return None;
|
||||
}
|
||||
pos += 4;
|
||||
|
||||
// Copy the entire question from query
|
||||
resp.extend_from_slice(&query[question_start..pos]);
|
||||
|
||||
// Answer section: A record
|
||||
// Name pointer: 0xC00C points to offset 12 (the question name)
|
||||
resp.push(0xC0);
|
||||
resp.push(0x0C);
|
||||
// TYPE: A (1)
|
||||
resp.push(0x00);
|
||||
resp.push(0x01);
|
||||
// CLASS: IN (1)
|
||||
resp.push(0x00);
|
||||
resp.push(0x01);
|
||||
// TTL: 300 seconds
|
||||
resp.push(0x00);
|
||||
resp.push(0x00);
|
||||
resp.push(0x01);
|
||||
resp.push(0x2C);
|
||||
// RDLENGTH: 4 (IPv4 address)
|
||||
resp.push(0x00);
|
||||
resp.push(0x04);
|
||||
// RDATA: canary IP
|
||||
let octets = canary_ip.octets();
|
||||
resp.extend_from_slice(&octets);
|
||||
|
||||
Some(resp)
|
||||
}
|
||||
|
||||
/// Default canary IP address.
|
||||
pub fn default_canary_ip() -> Ipv4Addr {
|
||||
DEFAULT_CANARY_IP
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn extract_simple_qname() {
|
||||
// DNS query for "example.com" — label format: 7example3com0
|
||||
let mut msg = vec![0u8; 12]; // header
|
||||
msg.push(7); // "example" length
|
||||
msg.extend_from_slice(b"example");
|
||||
msg.push(3); // "com" length
|
||||
msg.extend_from_slice(b"com");
|
||||
msg.push(0); // terminator
|
||||
msg.extend_from_slice(&[0, 1, 0, 1]); // QTYPE=A, QCLASS=IN
|
||||
|
||||
assert_eq!(extract_qname(&msg), "example.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_empty_message() {
|
||||
assert_eq!(extract_qname(&[0u8; 8]), "<empty>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_response_valid() {
|
||||
let mut query = vec![0xAB, 0xCD]; // Transaction ID
|
||||
query.extend_from_slice(&[0x01, 0x00]); // Flags (standard query)
|
||||
query.extend_from_slice(&[0, 1, 0, 0, 0, 0, 0, 0]); // QDCOUNT=1
|
||||
query.push(3); // "foo"
|
||||
query.extend_from_slice(b"foo");
|
||||
query.push(0); // terminator
|
||||
query.extend_from_slice(&[0, 1, 0, 1]); // QTYPE=A, QCLASS=IN
|
||||
|
||||
let resp = build_response(&query, Ipv4Addr::new(10, 0, 0, 200)).unwrap();
|
||||
// Check transaction ID preserved
|
||||
assert_eq!(resp[0], 0xAB);
|
||||
assert_eq!(resp[1], 0xCD);
|
||||
// Check ANCOUNT = 1
|
||||
assert_eq!(resp[6], 0x00);
|
||||
assert_eq!(resp[7], 0x01);
|
||||
// Check canary IP at end
|
||||
let ip_start = resp.len() - 4;
|
||||
assert_eq!(&resp[ip_start..], &[10, 0, 0, 200]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_response_too_short() {
|
||||
assert!(build_response(&[0u8; 6], Ipv4Addr::LOCALHOST).is_none());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue