use just_shield::dns_observer::{RelayConfig, serve};
use std::net::UdpSocket;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
fn query_packet(name: &str) -> Vec<u8> {
let mut p = vec![
0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
for label in name.split('.') {
p.push(label.len() as u8);
p.extend_from_slice(label.as_bytes());
}
p.push(0x00);
p.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
p
}
#[test]
fn relay_records_domain_and_forwards_response() {
let stop = Arc::new(AtomicBool::new(false));
let upstream = UdpSocket::bind("127.0.0.1:0").unwrap();
upstream
.set_read_timeout(Some(Duration::from_millis(200)))
.unwrap();
let upstream_addr = upstream.local_addr().unwrap().to_string();
let up_stop = stop.clone();
let upstream_thread = std::thread::spawn(move || {
let mut buf = [0u8; 1500];
while !up_stop.load(Ordering::Relaxed) {
if let Ok((n, from)) = upstream.recv_from(&mut buf) {
let mut resp = buf[..n].to_vec();
resp.extend_from_slice(&[0xCA, 0xFE]);
let _ = upstream.send_to(&resp, from);
}
}
});
let probe = UdpSocket::bind("127.0.0.1:0").unwrap();
let listen_addr = probe.local_addr().unwrap().to_string();
drop(probe);
let record = std::env::temp_dir().join(format!("js-dns-test-{}.txt", std::process::id()));
let config = RelayConfig {
listen: listen_addr.clone(),
upstream: upstream_addr,
job: "build".to_string(),
record_path: record.clone(),
stop: stop.clone(),
};
let relay = std::thread::spawn(move || {
let _ = serve(&config);
});
let client = UdpSocket::bind("127.0.0.1:0").unwrap();
client
.set_read_timeout(Some(Duration::from_millis(200)))
.unwrap();
let query = query_packet("crates.io");
let mut buf = [0u8; 1500];
let mut got = None;
let send_deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < send_deadline {
client.send_to(&query, &listen_addr).unwrap();
if let Ok((n, _)) = client.recv_from(&mut buf) {
got = Some(n);
break;
}
}
let n = got.expect("업스트림 응답이 돌아와야 한다");
assert_eq!(
&buf[n - 2..n],
&[0xCA, 0xFE],
"업스트림 응답이 전달되지 않음"
);
let deadline = Instant::now() + Duration::from_secs(3);
let mut content = String::new();
while Instant::now() < deadline {
if let Ok(c) = std::fs::read_to_string(&record)
&& c.contains("crates.io")
{
content = c;
break;
}
std::thread::sleep(Duration::from_millis(50));
}
stop.store(true, Ordering::Relaxed);
let _ = relay.join();
let _ = upstream_thread.join();
let _ = std::fs::remove_file(&record);
assert!(
content.contains("job build"),
"기록에 잡 이름이 없음:\n{content}"
);
assert!(
content.contains("crates.io"),
"기록에 도메인이 없음:\n{content}"
);
}