Skip to content

Commit 680caa7

Browse files
integration: integrate dns module to spawn a thread that polls dns events
Signed-off-by: saiaunghlyanhtet <saiaunghlyanhtet2003@gmail.com>
1 parent 44dcaab commit 680caa7

2 files changed

Lines changed: 142 additions & 2 deletions

File tree

src/bpf_user/maps.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ impl RuleMetadata {
189189
} else {
190190
Some(self.dst_port)
191191
},
192+
domain: None,
192193
}
193194
}
194195
}
@@ -296,6 +297,7 @@ impl RuleMetadataV6 {
296297
} else {
297298
Some(self.dst_port)
298299
},
300+
domain: None,
299301
}
300302
}
301303
}
@@ -310,6 +312,8 @@ pub struct BpfMaps<'a> {
310312
pub metadata_v6: Option<Map<'a>>,
311313
#[allow(dead_code)]
312314
pub stats_v6: Option<Map<'a>>,
315+
// DNS FQDN support
316+
pub dns_events: Option<Map<'a>>,
313317
}
314318

315319
impl<'a> BpfMaps<'a> {
@@ -321,6 +325,7 @@ impl<'a> BpfMaps<'a> {
321325
let mut rules_v6_map = None;
322326
let mut metadata_v6_map = None;
323327
let mut stats_v6_map = None;
328+
let mut dns_events_map = None;
324329

325330
for map in obj.maps() {
326331
let name = map.name().to_string_lossy();
@@ -332,6 +337,7 @@ impl<'a> BpfMaps<'a> {
332337
"rules_v6_map" => rules_v6_map = Some(map),
333338
"rule_metadata_v6_map" => metadata_v6_map = Some(map),
334339
"rule_stats_v6_map" => stats_v6_map = Some(map),
340+
"dns_events" => dns_events_map = Some(map),
335341
_ => {}
336342
}
337343
}
@@ -349,6 +355,7 @@ impl<'a> BpfMaps<'a> {
349355
rules_v6: rules_v6_map,
350356
metadata_v6: metadata_v6_map,
351357
stats_v6: stats_v6_map,
358+
dns_events: dns_events_map,
352359
}
353360
}
354361

src/main.rs

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::io;
1414
use std::path::PathBuf;
1515

1616
mod bpf_user;
17+
mod dns;
1718
mod models;
1819
mod policy;
1920
mod state;
@@ -142,6 +143,8 @@ async fn run_firewall(interface: &str, policy: Option<PathBuf>) -> Result<()> {
142143
let _loader = BpfLoader::new(interface)?;
143144
println!("✓ XDP program attached to {}", interface);
144145

146+
let mut fqdn_rules = Vec::new();
147+
145148
if let Some(policy_path) = policy {
146149
println!(
147150
"\nLoading rules from policy file: {}",
@@ -155,6 +158,15 @@ async fn run_firewall(interface: &str, policy: Option<PathBuf>) -> Result<()> {
155158
let maps = open_pinned_maps()?;
156159

157160
for policy_rule in &policy.rules {
161+
if policy_rule.is_fqdn_rule() {
162+
println!(
163+
"✓ Registered FQDN rule '{}': domain={} (DNS monitor will resolve)",
164+
policy_rule.name,
165+
policy_rule.domain.as_deref().unwrap_or("?")
166+
);
167+
continue;
168+
}
169+
158170
RulesState::add_rule(&maps, policy_rule)?;
159171

160172
let rule = policy_rule.to_rule()?;
@@ -169,7 +181,21 @@ async fn run_firewall(interface: &str, policy: Option<PathBuf>) -> Result<()> {
169181
);
170182
}
171183

172-
println!("\nSuccessfully loaded {} rules!", policy.rules.len());
184+
fqdn_rules = dns::monitor::extract_fqdn_rules(&policy.rules);
185+
let ip_rule_count = policy.rules.iter().filter(|r| !r.is_fqdn_rule()).count();
186+
println!("\nSuccessfully loaded {} rules!", ip_rule_count);
187+
188+
if !fqdn_rules.is_empty() {
189+
println!(
190+
" + {} FQDN rule(s) registered (resolving domains now...)",
191+
fqdn_rules.len()
192+
);
193+
dns::monitor::pre_resolve_fqdn_rules(&fqdn_rules, &maps);
194+
}
195+
}
196+
197+
if !fqdn_rules.is_empty() {
198+
start_dns_monitor(fqdn_rules);
173199
}
174200

175201
println!("\nXDP firewall is running. Use 'firebee ui' to view the TUI.");
@@ -178,6 +204,10 @@ async fn run_firewall(interface: &str, policy: Option<PathBuf>) -> Result<()> {
178204
// Keep the loader alive to prevent XDP detachment
179205
std::mem::forget(_loader);
180206

207+
// The XDP/TC programs remain attached even after this process exits.
208+
tokio::signal::ctrl_c().await.ok();
209+
println!("\nExiting. XDP firewall remains attached to the interface.");
210+
181211
Ok(())
182212
}
183213

@@ -272,6 +302,81 @@ async fn run_tui() -> Result<()> {
272302
Ok(())
273303
}
274304

305+
/// Start a background thread that monitors DNS responses from the BPF ring buffer,
306+
/// matches them against FQDN rules, and installs/removes BPF rules dynamically.
307+
fn start_dns_monitor(fqdn_rules: Vec<dns::monitor::FqdnRule>) {
308+
use crate::dns::monitor::{handle_dns_event, sweep_expired_rules, DnsMonitorState};
309+
use libbpf_rs::RingBufferBuilder;
310+
use std::sync::{Arc, Mutex};
311+
312+
let state = Arc::new(Mutex::new(DnsMonitorState::new(fqdn_rules)));
313+
314+
let dns_state = Arc::clone(&state);
315+
std::thread::spawn(move || {
316+
let maps = match open_pinned_maps() {
317+
Ok(m) => m,
318+
Err(e) => {
319+
eprintln!("[dns] Failed to open maps: {}", e);
320+
return;
321+
}
322+
};
323+
324+
let dns_map = match &maps.dns_events {
325+
Some(m) => m,
326+
None => {
327+
eprintln!("[dns] dns_events map not found — rebuild BPF programs with 'make bpf'");
328+
return;
329+
}
330+
};
331+
332+
let mut rb_builder = RingBufferBuilder::new();
333+
let event_state = Arc::clone(&dns_state);
334+
let event_maps = match open_pinned_maps() {
335+
Ok(m) => m,
336+
Err(e) => {
337+
eprintln!("[dns] Failed to open maps for events: {}", e);
338+
return;
339+
}
340+
};
341+
342+
// Leak event_maps so it lives for the ring buffer callback's 'static lifetime
343+
let event_maps: &'static BpfMaps<'static> = Box::leak(Box::new(event_maps));
344+
345+
rb_builder
346+
.add(dns_map, move |data| {
347+
handle_dns_event(data, &event_state, event_maps);
348+
0
349+
})
350+
.expect("Failed to add dns_events to ring buffer");
351+
352+
let rb = rb_builder.build().expect("Failed to build DNS ring buffer");
353+
354+
println!("[dns] Monitor started — watching for DNS responses...");
355+
356+
let mut sweep_counter = 0u32;
357+
loop {
358+
if let Err(e) = rb.poll(std::time::Duration::from_millis(100)) {
359+
log::error!("DNS ring buffer poll error: {}", e);
360+
std::thread::sleep(std::time::Duration::from_millis(100));
361+
}
362+
363+
// Sweep expired entries every ~10 seconds
364+
sweep_counter += 1;
365+
if sweep_counter >= 100 {
366+
sweep_counter = 0;
367+
let sweep_maps = match open_pinned_maps() {
368+
Ok(m) => m,
369+
Err(e) => {
370+
log::error!("DNS monitor: failed to open maps for sweep: {}", e);
371+
continue;
372+
}
373+
};
374+
sweep_expired_rules(&dns_state, &sweep_maps);
375+
}
376+
}
377+
});
378+
}
379+
275380
async fn add_rules_from_policy(
276381
interface: Option<String>,
277382
policy_path: PathBuf,
@@ -304,6 +409,15 @@ async fn add_rules_from_policy(
304409
};
305410

306411
for policy_rule in &policy.rules {
412+
if policy_rule.is_fqdn_rule() {
413+
println!(
414+
"✓ Registered FQDN rule '{}': domain={} (DNS monitor will resolve)",
415+
policy_rule.name,
416+
policy_rule.domain.as_deref().unwrap_or("?")
417+
);
418+
continue;
419+
}
420+
307421
RulesState::add_rule(&maps, policy_rule)?;
308422

309423
let rule = policy_rule.to_rule()?;
@@ -318,7 +432,20 @@ async fn add_rules_from_policy(
318432
);
319433
}
320434

321-
println!("\nSuccessfully added {} rules!", policy.rules.len());
435+
let fqdn_rules = dns::monitor::extract_fqdn_rules(&policy.rules);
436+
let has_fqdn = !fqdn_rules.is_empty();
437+
let ip_rule_count = policy.rules.iter().filter(|r| !r.is_fqdn_rule()).count();
438+
println!("\nSuccessfully added {} rules!", ip_rule_count);
439+
440+
if has_fqdn {
441+
println!(
442+
" + {} FQDN rule(s) registered (resolving domains now...)",
443+
fqdn_rules.len()
444+
);
445+
dns::monitor::pre_resolve_fqdn_rules(&fqdn_rules, &maps);
446+
start_dns_monitor(fqdn_rules);
447+
}
448+
322449
println!("Rules are now active in the eBPF firewall.");
323450

324451
if _loader.is_some() {
@@ -327,6 +454,12 @@ async fn add_rules_from_policy(
327454
std::mem::forget(_loader); // Intentionally leak to keep XDP attached
328455
}
329456

457+
if has_fqdn {
458+
println!("\nDNS monitor is running for FQDN rules.");
459+
tokio::signal::ctrl_c().await.ok();
460+
println!("\nExiting. FQDN rules installed in BPF maps remain active.");
461+
}
462+
330463
Ok(())
331464
}
332465

0 commit comments

Comments
 (0)