From 594dabcab85e7320c1b00e0d9e261dc899130de7 Mon Sep 17 00:00:00 2001 From: Revertron Date: Fri, 1 Apr 2022 13:03:32 +0200 Subject: [PATCH] Implemented resolution of domain records through NS-servers. Updated dependencies. --- Cargo.lock | 21 +++-- Cargo.toml | 8 +- src/blockchain/filter.rs | 197 +++++++++++++++++++++++++++------------ src/dns/client.rs | 2 + src/webview/scripts.js | 2 +- 5 files changed, 160 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32aac46..c775937 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "alfis" -version = "0.6.11" +version = "0.6.12" dependencies = [ "base64", "bincode", @@ -725,9 +725,9 @@ checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", ] @@ -759,14 +759,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", "miow", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -1313,9 +1314,9 @@ dependencies = [ [[package]] name = "tinyfiledialogs" -version = "3.9.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc577626a3c26e4e1d470dbe5fe33d6fabc14e57114cb377acdb4da1a17dde9" +checksum = "e25fa0bc43a6566e2cc6d7ac96df3fa5a57beba34445bead1b368ba8fe9ca568" dependencies = [ "cc", "libc", @@ -1469,6 +1470,12 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.78" diff --git a/Cargo.toml b/Cargo.toml index 463ca8b..871f165 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.6.11" +version = "0.6.12" authors = ["Revertron "] edition = "2021" build = "build.rs" @@ -12,7 +12,7 @@ exclude = ["blockchain.db", "alfis.toml"] [dependencies] getopts = "0.2.21" -log = "0.4.14" +log = "0.4.16" simplelog = "0.11.2" toml = "0.5.8" digest = "0.10.2" @@ -37,7 +37,7 @@ rand = { version = "0.8.5", package = "rand" } rand-old = { package = "rand", version = "0.7.0" } # For ed25519-dalek sqlite = "0.26.0" uuid = { version = "0.8.2", features = ["serde", "v4"] } -mio = { version = "0.8.0", features = ["os-poll", "net"] } +mio = { version = "0.8.2", features = ["os-poll", "net"] } ureq = { version = "2.4", optional = true } lru = "0.7.3" derive_more = "0.99.17" @@ -45,7 +45,7 @@ lazy_static = "1.4.0" # Optional dependencies regulated by features web-view = { version = "0.7.3", features = [], optional = true } -tinyfiledialogs = { version = "3.9", optional = true } +tinyfiledialogs = { version = "3.9.1", optional = true } open = { version = "2.1.1", optional = true } [target.'cfg(windows)'.dependencies] diff --git a/src/blockchain/filter.rs b/src/blockchain/filter.rs index ac5e280..3e9d310 100644 --- a/src/blockchain/filter.rs +++ b/src/blockchain/filter.rs @@ -1,3 +1,4 @@ +use std::net::{IpAddr, SocketAddr}; use std::sync::{Arc, Mutex}; #[allow(unused_imports)] @@ -7,6 +8,10 @@ use crate::blockchain::transaction::DomainData; use crate::dns::filter::DnsFilter; use crate::dns::protocol::{DnsPacket, DnsQuestion, DnsRecord, QueryType, ResultCode, TransientTtl}; use crate::Context; +use crate::dns::client::{DnsClient, DnsNetworkClient}; + +const NAME_SERVER: &str = "ns.alfis.name"; +const SERVER_ADMIN: &str = "admin.alfis.name"; pub struct BlockchainFilter { context: Arc> @@ -16,14 +21,128 @@ impl BlockchainFilter { pub fn new(context: Arc>) -> Self { BlockchainFilter { context } } -} -const NAME_SERVER: &str = "ns.alfis.name"; -const SERVER_ADMIN: &str = "admin.alfis.name"; + fn add_soa_record(zone: String, serial: u32, packet: &mut DnsPacket) { + packet.authorities.push(DnsRecord::SOA { + domain: zone, + m_name: String::from(NAME_SERVER), + r_name: String::from(SERVER_ADMIN), + serial, + refresh: 3600, + retry: 300, + expire: 604800, + minimum: 60, + ttl: TransientTtl(60) + }); + } + + fn get_zone_response(&self, zone: &str, serial: u32, packet: &mut DnsPacket) -> bool { + let have_zone = self.context.lock().unwrap().chain.is_available_zone(zone); + if have_zone { + BlockchainFilter::add_soa_record(zone.to_owned(), serial, packet); + } + have_zone + } + + fn lookup_from_ns(qname: &str, qtype: QueryType, servers: &Vec) -> Option { + let port = 10000 + (rand::random::() % 50000); + let mut dns_client = DnsNetworkClient::new(port); + dns_client.run().unwrap(); + + for server in servers { + let addr = SocketAddr::new(server.to_owned(), 53); + if let Ok(res) = dns_client.send_udp_query(qname, qtype, addr, false) { + dns_client.stop(); + return Some(res); + } + } + dns_client.stop(); + None + } + + fn create_packet(&self, qname: &str, qtype: QueryType, zone: String, answers: Vec) -> Option { + if !answers.is_empty() { + // Create DnsPacket + let mut packet = DnsPacket::new(); + packet.header.authoritative_answer = true; + packet.questions.push(DnsQuestion::new(String::from(qname), qtype)); + for answer in answers { + packet.answers.push(answer); + } + packet.authorities.push(DnsRecord::NS { domain: zone, host: String::from(NAME_SERVER), ttl: TransientTtl(600) }); + //trace!("Returning packet: {:?}", &packet); + Some(packet) + } else { + // Create DnsPacket + let mut packet = DnsPacket::new(); + packet.header.authoritative_answer = true; + packet.header.rescode = ResultCode::NOERROR; + packet.questions.push(DnsQuestion::new(String::from(qname), qtype)); + let serial = self.context.lock().unwrap().chain.get_soa_serial(); + BlockchainFilter::add_soa_record(zone, serial, &mut packet); + //trace!("Returning packet: {:?}", &packet); + Some(packet) + } + } + + fn resolve_by_ns(qname: &str, qtype: QueryType, top_domain: &String, data: &DomainData) -> (bool, Option) { + // First we search for NS records, collecting nameserver domains + let mut hosts = Vec::new(); + for record in data.records.iter() { + if record.get_querytype() == QueryType::NS { + match &record { + DnsRecord::NS { domain, host, .. } if domain == "@" => { + hosts.push(host.to_owned()); + } + _ => () + } + } + } + + if hosts.is_empty() { + return (false, None); + } + + // Searching glue records + let mut servers = Vec::new(); + for record in data.records.iter() { + match &record { + DnsRecord::A { domain, addr, .. } => { + let domain = format!("{}.{}", &domain, &top_domain); + for host in &hosts { + if &domain == host { + servers.push(IpAddr::from(addr.clone())); + } + } + } + DnsRecord::AAAA { domain, addr, .. } => { + let domain = format!("{}.{}", &domain, &top_domain); + for host in &hosts { + if &domain == host { + servers.push(IpAddr::from(addr.clone())); + } + } + } + _ => () + } + } + + if !servers.is_empty() { + trace!("Found NS servers for domain {}: {:?}", &qname, &servers); + let answer = BlockchainFilter::lookup_from_ns(qname, qtype, &servers); + if let Some(packet) = &answer { + trace!("Resolved {:?} from NS: {:?}", (qname, qtype), &packet.answers); + } + return (true, answer); + } + + (false, None) + } +} impl DnsFilter for BlockchainFilter { fn lookup(&self, qname: &str, qtype: QueryType) -> Option { - let search; + let top_domain; let subdomain; let parts: Vec<&str> = qname.rsplitn(3, '.').collect(); match parts.len() { @@ -36,22 +155,22 @@ impl DnsFilter for BlockchainFilter { return None; } 2 => { - search = format!("{}.{}", parts[1], parts[0]); + top_domain = format!("{}.{}", parts[1], parts[0]); subdomain = String::new(); } _ => { - search = format!("{}.{}", parts[1], parts[0]); + top_domain = format!("{}.{}", parts[1], parts[0]); subdomain = String::from(parts[2]); } } //trace!("Searching record type '{:?}', name '{}' for domain '{}'", &qtype, &subdomain, &search); - let data = self.context.lock().unwrap().chain.get_domain_info(&search); + let data = self.context.lock().unwrap().chain.get_domain_info(&top_domain); let zone = parts[0].to_owned(); match data { None => { if self.context.lock().unwrap().chain.is_available_zone(&zone) { - trace!("Not found data for domain {}", &search); + trace!("Not found data for domain {}", &top_domain); // Create DnsPacket let mut packet = DnsPacket::new(); packet.questions.push(DnsQuestion::new(String::from(qname), qtype)); @@ -64,13 +183,20 @@ impl DnsFilter for BlockchainFilter { } } Some(data) => { - trace!("Found data for domain {}", &search); + trace!("Found data for domain {}", &top_domain); let mut data: DomainData = match serde_json::from_str(&data) { Err(_) => { return None; } Ok(data) => data }; + + // Check if this domain has NS records and needs to resolve all records through them + let (has_ns, result) = Self::resolve_by_ns(qname, qtype, &top_domain, &data); + if has_ns { + return result; + } + let mut answers: Vec = Vec::new(); let a_record = qtype == QueryType::A || qtype == QueryType::AAAA; for mut record in data.records.iter_mut() { @@ -93,7 +219,7 @@ impl DnsFilter for BlockchainFilter { match record.get_domain() { None => {} Some(domain) => { - if domain == search { + if domain == top_domain { answers.push(record.clone()); } else if domain == subdomain { match &mut record { @@ -117,13 +243,13 @@ impl DnsFilter for BlockchainFilter { } } if answers.is_empty() { - // If there are no records found we search for *.domain.ltd record + // If there are no records found we search for *.domain.tld record for mut record in data.records { if record.get_querytype() == qtype { match record.get_domain() { None => {} Some(domain) => { - if domain == search { + if domain == top_domain { answers.push(record.clone()); } else if domain == "*" { match &mut record { @@ -149,28 +275,7 @@ impl DnsFilter for BlockchainFilter { } //debug!("Answers: {:?}", &answers); - return if !answers.is_empty() { - // Create DnsPacket - let mut packet = DnsPacket::new(); - packet.header.authoritative_answer = true; - packet.questions.push(DnsQuestion::new(String::from(qname), qtype)); - for answer in answers { - packet.answers.push(answer); - } - packet.authorities.push(DnsRecord::NS { domain: zone, host: String::from(NAME_SERVER), ttl: TransientTtl(600) }); - //trace!("Returning packet: {:?}", &packet); - Some(packet) - } else { - // Create DnsPacket - let mut packet = DnsPacket::new(); - packet.header.authoritative_answer = true; - packet.header.rescode = ResultCode::NOERROR; - packet.questions.push(DnsQuestion::new(String::from(qname), qtype)); - let serial = self.context.lock().unwrap().chain.get_soa_serial(); - BlockchainFilter::add_soa_record(zone, serial, &mut packet); - //trace!("Returning packet: {:?}", &packet); - Some(packet) - }; + return self.create_packet(qname, qtype, zone, answers); } } @@ -178,30 +283,6 @@ impl DnsFilter for BlockchainFilter { } } -impl BlockchainFilter { - fn add_soa_record(zone: String, serial: u32, packet: &mut DnsPacket) { - packet.authorities.push(DnsRecord::SOA { - domain: zone, - m_name: String::from(NAME_SERVER), - r_name: String::from(SERVER_ADMIN), - serial, - refresh: 3600, - retry: 300, - expire: 604800, - minimum: 60, - ttl: TransientTtl(60) - }); - } - - fn get_zone_response(&self, zone: &str, serial: u32, packet: &mut DnsPacket) -> bool { - let have_zone = self.context.lock().unwrap().chain.is_available_zone(zone); - if have_zone { - BlockchainFilter::add_soa_record(zone.to_owned(), serial, packet); - } - have_zone - } -} - #[cfg(test)] mod tests { // TODO write tests for this filter diff --git a/src/dns/client.rs b/src/dns/client.rs index 6f4ea0a..fffd893 100644 --- a/src/dns/client.rs +++ b/src/dns/client.rs @@ -442,6 +442,8 @@ impl HttpsDnsClient { } dns_client.stop(); + result.sort(); + result.dedup(); let addrs = result .into_iter() .map(|ip| SocketAddr::new(ip, 443)) diff --git a/src/webview/scripts.js b/src/webview/scripts.js index 8efdcda..02682cc 100644 --- a/src/webview/scripts.js +++ b/src/webview/scripts.js @@ -49,7 +49,7 @@ function refreshRecordsList() { var data = value.addr; if (value.type == "MX") { data = value.priority + " " + value.host; - } else if (value.type == "CNAME") { + } else if (value.type == "CNAME" || value.type == "NS") { data = value.host; } else if (value.type == "TXT") { data = value.data;