123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- /*************************************************************************
- *
- * Copyright (C) 2018-2025 Ruilin Peng (Nick) <[email protected]>.
- *
- * smartdns is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * smartdns is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- use smartdns_ui::data_server::DataServer;
- use smartdns_ui::db::*;
- use smartdns_ui::dns_log;
- use smartdns_ui::plugin::*;
- use smartdns_ui::smartdns::*;
- use std::io::Write;
- use std::sync::Arc;
- use tempfile::TempDir;
- static INSTANCE_LOCK: std::sync::RwLock<()> = std::sync::RwLock::new(());
- pub struct InstanceLockGuard<'a> {
- _read_guard: Option<std::sync::RwLockReadGuard<'a, ()>>,
- _write_guard: Option<std::sync::RwLockWriteGuard<'a, ()>>,
- }
- impl<'a> InstanceLockGuard<'a> {
- pub fn new_read_guard() -> Self {
- Self {
- _read_guard: Some(INSTANCE_LOCK.read().unwrap()),
- _write_guard: None,
- }
- }
- pub fn new_write_guard() -> Self {
- Self {
- _read_guard: None,
- _write_guard: Some(INSTANCE_LOCK.write().unwrap()),
- }
- }
- }
- pub struct TestDnsRequest {
- pub domain: String,
- pub group_name: String,
- pub qtype: u32,
- pub qclass: i32,
- pub id: u16,
- pub rcode: u16,
- pub query_time: i32,
- pub query_timestamp: u64,
- pub ping_time: f64,
- pub is_blocked: bool,
- pub is_cached: bool,
- pub remote_mac: [u8; 6],
- pub remote_addr: String,
- pub local_addr: String,
- pub prefetch_request: bool,
- pub dualstack_request: bool,
- pub drop_callback: Option<Box<dyn Fn() + Send + Sync>>,
- }
- #[allow(dead_code)]
- impl TestDnsRequest {
- pub fn new() -> Self {
- TestDnsRequest {
- domain: "".to_string(),
- group_name: "default".to_string(),
- qtype: 1,
- qclass: 1,
- id: 0,
- rcode: 2,
- query_time: 0,
- query_timestamp: get_utc_time_ms(),
- ping_time: -0.1 as f64,
- is_blocked: false,
- is_cached: false,
- remote_mac: [0; 6],
- remote_addr: "127.0.0.1".to_string(),
- local_addr: "127.0.0.1".to_string(),
- prefetch_request: false,
- dualstack_request: false,
- drop_callback: None,
- }
- }
- }
- #[allow(dead_code)]
- impl DnsRequest for TestDnsRequest {
- fn get_group_name(&self) -> String {
- self.group_name.clone()
- }
- fn get_domain(&self) -> String {
- self.domain.clone()
- }
- fn get_qtype(&self) -> u32 {
- self.qtype
- }
- fn get_qclass(&self) -> i32 {
- self.qclass
- }
- fn get_id(&self) -> u16 {
- self.id
- }
- fn get_rcode(&self) -> u16 {
- self.rcode
- }
- fn get_query_time(&self) -> i32 {
- self.query_time
- }
- fn get_query_timestamp(&self) -> u64 {
- self.query_timestamp
- }
- fn get_ping_time(&self) -> f64 {
- self.ping_time
- }
- fn get_is_blocked(&self) -> bool {
- self.is_blocked
- }
- fn get_is_cached(&self) -> bool {
- self.is_cached
- }
- fn get_remote_mac(&self) -> [u8; 6] {
- self.remote_mac
- }
- fn get_remote_addr(&self) -> String {
- self.remote_addr.clone()
- }
- fn get_local_addr(&self) -> String {
- self.local_addr.clone()
- }
- fn is_prefetch_request(&self) -> bool {
- self.prefetch_request
- }
- fn is_dualstack_request(&self) -> bool {
- self.dualstack_request
- }
- }
- impl Drop for TestDnsRequest {
- fn drop(&mut self) {
- if let Some(f) = &self.drop_callback {
- f();
- }
- }
- }
- unsafe impl Send for TestDnsRequest {}
- unsafe impl Sync for TestDnsRequest {}
- #[allow(dead_code)]
- struct TestSmartDnsConfigItem {
- pub key: String,
- pub value: String,
- }
- pub struct TestSmartDnsServer {
- confs: Vec<TestSmartDnsConfigItem>,
- is_started: bool,
- workdir: String,
- thread: Option<std::thread::JoinHandle<()>>,
- }
- impl TestSmartDnsServer {
- pub fn new() -> Self {
- let mut server = TestSmartDnsServer {
- confs: Vec::new(),
- is_started: false,
- workdir: "/tmp/smartdns-test.conf".to_string(),
- thread: None,
- };
- server.add_conf("bind", ":66603");
- server.add_conf("log-level", "debug");
- server.add_conf("log-num", "0");
- server.add_conf("cache-persist", "no");
- server
- }
- pub fn set_workdir(&mut self, workdir: &str) {
- self.workdir = workdir.to_string();
- }
- pub fn add_conf(&mut self, key: &str, value: &str) {
- self.confs.push(TestSmartDnsConfigItem {
- key: key.to_string(),
- value: value.to_string(),
- });
- }
- fn gen_conf_file(&self) -> std::io::Result<String> {
- let file = self.workdir.clone() + "/smartdns.conf";
- let mut f = std::fs::File::create(&file)?;
- for conf in self.confs.iter() {
- f.write_all(format!("{} {}\n", conf.key, conf.value).as_bytes())?;
- }
- Ok(file)
- }
- pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
- let conf_file = self.gen_conf_file()?;
- let t = std::thread::spawn(move || {
- dns_log!(LogLevel::ERROR, "smartdns server run start...");
- smartdns_ui::smartdns::smartdns_server_run(&conf_file).unwrap();
- dns_log!(LogLevel::ERROR, "smartdns server run exit...");
- });
- self.thread = Some(t);
- self.is_started = true;
- dns_log!(LogLevel::ERROR, "smartdns_server_run");
- Ok(())
- }
- pub fn stop(&mut self) {
- if !self.is_started {
- return;
- }
- self.is_started = false;
- smartdns_ui::smartdns::smartdns_server_stop();
- if self.thread.is_none() {
- return;
- }
- let _ = self.thread.take().unwrap().join();
- }
- }
- impl Drop for TestSmartDnsServer {
- fn drop(&mut self) {
- self.stop();
- }
- }
- pub struct TestServer {
- dns_server: TestSmartDnsServer,
- dns_server_enable: bool,
- plugin: Arc<SmartdnsPlugin>,
- args: Vec<String>,
- workdir: String,
- temp_dir: TempDir,
- www_root: String,
- is_started: bool,
- ip: String,
- is_https: bool,
- log_level: LogLevel,
- old_log_level: LogLevel,
- one_instance: bool,
- instance_lock_guard: Option<InstanceLockGuard<'static>>,
- }
- impl TestServer {
- pub fn new() -> Self {
- let mut server = TestServer {
- dns_server: TestSmartDnsServer::new(),
- dns_server_enable: false,
- plugin: SmartdnsPlugin::new(),
- args: Vec::new(),
- workdir: String::new(),
- temp_dir: TempDir::with_prefix("smartdns-ui-").unwrap(),
- www_root: String::new(),
- is_started: false,
- ip: "http://127.0.0.1:0".to_string(),
- is_https: false,
- log_level: LogLevel::INFO,
- old_log_level: LogLevel::INFO,
- one_instance: false,
- instance_lock_guard: None,
- };
- server.workdir = server.temp_dir.path().to_str().unwrap().to_string();
- server.dns_server.set_workdir(&server.workdir);
- server.get_data_server().set_recv_in_batch(false);
- server
- }
- fn setup_default_args(&mut self) {
- self.args.insert(0, "--ip".to_string());
- self.args.insert(1, self.ip.clone());
- self.args.insert(0, "--data-dir".to_string());
- self.args.insert(1, self.workdir.clone() + "/data.db");
- self.args.insert(0, "--www-root".to_string());
- self.www_root = self.workdir.clone() + "/www";
- self.args.insert(1, self.www_root.clone());
- self.args.insert(0, "smartdns-ui".to_string());
- dns_log!(LogLevel::INFO, "workdir: {}", self.workdir);
- }
- #[allow(dead_code)]
- pub fn get_url(&self, path: &str) -> String {
- self.ip.clone() + path
- }
- pub fn get_host(&self) -> String {
- self.ip.clone()
- }
- #[allow(dead_code)]
- pub fn get_www_root(&self) -> &String {
- &self.www_root
- }
- fn create_workdir(&self) -> std::io::Result<()> {
- std::fs::create_dir_all(&self.workdir)?;
- std::fs::create_dir_all(&self.www_root)?;
- Ok(())
- }
- fn remove_workdir(&self) -> std::io::Result<()> {
- let r = std::fs::remove_dir_all(&self.workdir);
- return r;
- }
- #[allow(dead_code)]
- pub fn add_mock_server_conf(&mut self, key: &str, value: &str) {
- self.dns_server.add_conf(key, value);
- }
- #[allow(dead_code)]
- pub fn enable_mock_server(&mut self) {
- self.dns_server_enable = true;
- self.set_one_instance(true);
- }
- #[allow(dead_code)]
- pub fn add_args(&mut self, args: Vec<String>) {
- for arg in args.iter() {
- self.args.push(arg.clone());
- }
- }
- #[allow(dead_code)]
- pub fn send_test_dnsrequest(
- &mut self,
- mut request: TestDnsRequest,
- ) -> Result<(), Box<dyn std::error::Error>> {
- let batch_mode = self.get_data_server().get_recv_in_batch();
- let (tx, rx) = std::sync::mpsc::channel();
- let request_drop_callback = move || {
- tx.send(()).unwrap();
- };
- if batch_mode == false {
- request.drop_callback = Some(Box::new(request_drop_callback));
- }
- let ret = self.plugin.query_complete(Box::new(request));
- if let Err(e) = ret {
- dns_log!(LogLevel::ERROR, "send_test_dnsrequest error: {:?}", e);
- return Err(e);
- }
- if batch_mode == false {
- rx.recv().unwrap();
- }
- Ok(())
- }
- #[allow(dead_code)]
- pub fn new_mock_domain_record(&self) -> DomainData {
- DomainData {
- id: 0,
- timestamp: smartdns_ui::smartdns::get_utc_time_ms(),
- domain: "example.com".to_string(),
- domain_type: 1,
- client: "127.0.0.1".to_string(),
- domain_group: "default".to_string(),
- reply_code: 0,
- query_time: 0,
- ping_time: -0.1 as f64,
- is_blocked: false,
- is_cached: false,
- }
- }
- #[allow(dead_code)]
- pub fn get_data_server(&self) -> Arc<DataServer> {
- self.plugin.get_data_server()
- }
- #[allow(dead_code)]
- pub fn add_domain_record(
- &mut self,
- record: &DomainData,
- ) -> Result<(), Box<dyn std::error::Error>> {
- self.plugin.get_data_server().insert_domain(record)
- }
- pub fn set_log_level(&mut self, level: LogLevel) {
- self.log_level = level;
- }
- fn init_server(&mut self) -> Result<(), Box<dyn std::error::Error>> {
- self.create_workdir()?;
- self.old_log_level = smartdns_ui::smartdns::dns_log_get_level();
- smartdns_ui::smartdns::dns_log_set_level(self.log_level);
- Ok(())
- }
- #[allow(dead_code)]
- pub fn set_https(&mut self, enable: bool) {
- self.is_https = enable;
- if enable {
- self.ip = "https://127.0.0.1:0".to_string();
- } else {
- self.ip = "http://127.0.0.1:0".to_string();
- }
- }
- pub fn set_one_instance(&mut self, one_instance: bool) {
- self.one_instance = one_instance;
- }
- pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
- if self.one_instance {
- self.instance_lock_guard = Some(InstanceLockGuard::new_write_guard());
- if self.dns_server_enable {
- let ret = self.dns_server.start();
- if let Err(e) = ret {
- dns_log!(LogLevel::ERROR, "start dns server failed: {:?}", e);
- return Err(e);
- }
- }
- } else {
- self.instance_lock_guard = Some(InstanceLockGuard::new_read_guard());
- }
- self.setup_default_args();
- dns_log!(LogLevel::INFO, "TestServer start");
- let ret = self.init_server();
- if let Err(e) = ret {
- dns_log!(LogLevel::ERROR, "init server failed: {:?}", e);
- return Err(e);
- }
- let result = self.plugin.start(&self.args);
- if let Err(e) = result {
- dns_log!(LogLevel::ERROR, "start error: {:?}", e);
- return Err(e);
- }
- let addr = self.plugin.get_http_server().get_local_addr();
- if addr.is_none() {
- return Err(Box::new(std::io::Error::new(
- std::io::ErrorKind::Other,
- "get local addr failed",
- )));
- }
- let addr = addr.unwrap();
- if self.is_https {
- self.ip = format!("https://{}:{}", addr.ip(), addr.port());
- } else {
- self.ip = format!("http://{}:{}", addr.ip(), addr.port());
- }
- self.is_started = true;
- Ok(())
- }
- pub fn stop(&mut self) {
- if !self.is_started {
- return;
- }
- dns_log!(LogLevel::INFO, "TestServer stop");
- self.plugin.stop();
- self.is_started = false;
- self.one_instance = false;
- smartdns_ui::smartdns::dns_log_set_level(self.old_log_level);
- self.dns_server.stop();
- self.instance_lock_guard = None;
- }
- }
- impl Drop for TestServer {
- fn drop(&mut self) {
- self.stop();
- let _ = self.remove_workdir();
- }
- }
|