diff --git a/Cargo.lock b/Cargo.lock index e27b1a8..40846bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ dependencies = [ [[package]] name = "alfis" -version = "0.8.7" +version = "0.8.8" dependencies = [ "bincode", "blakeout", diff --git a/Cargo.toml b/Cargo.toml index 89965cd..fd74931 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.8.7" +version = "0.8.8" authors = ["Revertron "] edition = "2021" build = "build.rs" diff --git a/src/blockchain/filter.rs b/src/blockchain/filter.rs index 32641f7..398b876 100644 --- a/src/blockchain/filter.rs +++ b/src/blockchain/filter.rs @@ -230,9 +230,11 @@ impl BlockchainFilter { impl DnsFilter for BlockchainFilter { fn lookup(&self, qname: &str, qtype: QueryType, recursive: bool) -> Option { + // Lowercase for case-insensitive lookup (blockchain stores domains as lowercase) + let qname_lower = qname.to_lowercase(); let top_domain; let subdomain; - let parts: Vec<&str> = qname.rsplitn(3, '.').collect(); + let parts: Vec<&str> = qname_lower.rsplitn(3, '.').collect(); match parts.len() { 1 => { let mut packet = DnsPacket::new(); diff --git a/src/dns/buffer.rs b/src/dns/buffer.rs index 3b61225..94cc48b 100644 --- a/src/dns/buffer.rs +++ b/src/dns/buffer.rs @@ -136,7 +136,7 @@ pub trait PacketBuffer { outstr.push_str(delim); let str_buffer = self.get_range(pos, len as usize)?; - outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + outstr.push_str(&String::from_utf8_lossy(str_buffer)); delim = "."; diff --git a/src/dns/cache.rs b/src/dns/cache.rs index 366325c..f476c63 100644 --- a/src/dns/cache.rs +++ b/src/dns/cache.rs @@ -132,7 +132,7 @@ impl DomainEntry { } } - pub fn fill_queryresult(&self, qtype: QueryType, result_vec: &mut Vec) { + pub fn fill_queryresult(&self, qname: &str, qtype: QueryType, result_vec: &mut Vec) { let now = Local::now(); let current_set = match self.record_types.get(&qtype) { @@ -149,7 +149,10 @@ impl DomainEntry { } if entry.record.get_querytype() == qtype { - result_vec.push(entry.record.clone()); + let mut record = entry.record.clone(); + // Preserve the original query case in the response + record.set_domain(qname.to_string()); + result_vec.push(record); } } } @@ -174,17 +177,21 @@ impl Cache { } fn fill_queryresult(&mut self, qname: &str, qtype: QueryType, result_vec: &mut Vec, increment_stats: bool) { - if let Some(domain_entry) = self.domain_entries.get_mut(qname).and_then(Arc::get_mut) { + // DNS is case-insensitive, so lowercase for cache lookup + let qname_lower = qname.to_lowercase(); + if let Some(domain_entry) = self.domain_entries.get_mut(&qname_lower).and_then(Arc::get_mut) { if increment_stats { domain_entry.hits += 1 } - domain_entry.fill_queryresult(qtype, result_vec); + domain_entry.fill_queryresult(qname, qtype, result_vec); } } pub fn lookup(&mut self, qname: &str, qtype: QueryType) -> Option { - match self.get_cache_state(qname, qtype) { + // DNS is case-insensitive, so lowercase for cache lookup + let qname_lower = qname.to_lowercase(); + match self.get_cache_state(&qname_lower, qtype) { CacheState::PositiveCache => { let mut qr = DnsPacket::new(); self.fill_queryresult(qname, qtype, &mut qr.answers, true); @@ -208,27 +215,31 @@ impl Cache { Some(x) => x, None => continue }; + // Store with a lowercase key for case-insensitive lookups + let domain_lower = domain.to_lowercase(); - if let Some(ref mut rs) = self.domain_entries.get_mut(&domain).and_then(Arc::get_mut) { + if let Some(ref mut rs) = self.domain_entries.get_mut(&domain_lower).and_then(Arc::get_mut) { rs.store_record(rec); continue; } - let mut rs = DomainEntry::new(domain.clone()); + let mut rs = DomainEntry::new(domain_lower.clone()); rs.store_record(rec); - self.domain_entries.insert(domain.clone(), Arc::new(rs)); + self.domain_entries.insert(domain_lower, Arc::new(rs)); } } pub fn store_nxdomain(&mut self, qname: &str, qtype: QueryType, ttl: u32) { - if let Some(ref mut rs) = self.domain_entries.get_mut(qname).and_then(Arc::get_mut) { + // Store with lowercase key for case-insensitive lookups + let qname_lower = qname.to_lowercase(); + if let Some(ref mut rs) = self.domain_entries.get_mut(&qname_lower).and_then(Arc::get_mut) { rs.store_nxdomain(qtype, ttl); return; } - let mut rs = DomainEntry::new(qname.to_string()); + let mut rs = DomainEntry::new(qname_lower.clone()); rs.store_nxdomain(qtype, ttl); - self.domain_entries.insert(qname.to_string(), Arc::new(rs)); + self.domain_entries.insert(qname_lower, Arc::new(rs)); } } diff --git a/src/dns/client.rs b/src/dns/client.rs index 392b1de..101e946 100644 --- a/src/dns/client.rs +++ b/src/dns/client.rs @@ -271,6 +271,8 @@ impl DnsClient for DnsNetworkClient { let pending_queries_lock = self.pending_queries.clone(); let stopped = Arc::clone(&self.stopped); + let match_case = self.enable_0x20; + Builder::new() .name("DnsNetworkClient-worker-thread-v4".into()) .spawn(move || { @@ -308,7 +310,8 @@ impl DnsClient for DnsNetworkClient { // Validate 0x20 encoding - response must match query case exactly if !packet.questions.is_empty() { let response_name = &packet.questions[0].name; - if response_name != &pending_query.query_name { + if (match_case && response_name != &pending_query.query_name) + || (pending_query.query_name.to_lowercase() != response_name.to_lowercase()) { trace!("Rejecting response with mismatched case: expected '{}', got '{}'", pending_query.query_name, response_name); continue; @@ -342,6 +345,8 @@ impl DnsClient for DnsNetworkClient { let pending_queries_lock = self.pending_queries.clone(); let stopped = Arc::clone(&self.stopped); + let match_case = self.enable_0x20; + Builder::new() .name("DnsNetworkClient-worker-thread-v6".into()) .spawn(move || { @@ -379,7 +384,8 @@ impl DnsClient for DnsNetworkClient { // Validate 0x20 encoding - response must match query case exactly if !packet.questions.is_empty() { let response_name = &packet.questions[0].name; - if response_name != &pending_query.query_name { + if (match_case && response_name != &pending_query.query_name) + || (pending_query.query_name.to_lowercase() != response_name.to_lowercase()) { trace!("Rejecting response with mismatched case: expected '{}', got '{}'", pending_query.query_name, response_name); continue; @@ -461,7 +467,7 @@ impl DnsClient for DnsNetworkClient { pub struct HttpsDnsClient { agent: Agent, /// Counter for assigning packet ids - seq: AtomicUsize, + seq: AtomicU16, } #[cfg(feature = "doh")] @@ -481,7 +487,7 @@ impl HttpsDnsClient { .max_idle_connections(16) .build(); let agent = Agent::with_parts(agent_config, DefaultConnector::default(), BootstrapResolver::new(servers)); - Self { agent, seq: AtomicUsize::new(1) } + Self { agent, seq: AtomicU16::new(rand::random::()) } } } diff --git a/src/dns/protocol.rs b/src/dns/protocol.rs index d9cda84..e925ab8 100644 --- a/src/dns/protocol.rs +++ b/src/dns/protocol.rs @@ -505,6 +505,23 @@ impl DnsRecord { } } + pub fn set_domain(&mut self, new_domain: String) { + match self { + DnsRecord::A { ref mut domain, .. } + | DnsRecord::AAAA { ref mut domain, .. } + | DnsRecord::NS { ref mut domain, .. } + | DnsRecord::CNAME { ref mut domain, .. } + | DnsRecord::SRV { ref mut domain, .. } + | DnsRecord::PTR { ref mut domain, .. } + | DnsRecord::MX { ref mut domain, .. } + | DnsRecord::UNKNOWN { ref mut domain, .. } + | DnsRecord::SOA { ref mut domain, .. } + | DnsRecord::TXT { ref mut domain, .. } + | DnsRecord::TLSA { ref mut domain, .. } => *domain = new_domain, + DnsRecord::OPT { .. } => {} // OPT records don't have a domain field + } + } + pub fn get_data(&self) -> Option { match *self { DnsRecord::A { ref addr, .. } => Some(addr.to_string()), diff --git a/src/dns/resolve.rs b/src/dns/resolve.rs index 394c653..a68c5a8 100644 --- a/src/dns/resolve.rs +++ b/src/dns/resolve.rs @@ -87,7 +87,7 @@ impl DnsResolver for ForwardingDnsResolver { fn perform(&mut self, qname: &str, qtype: QueryType) -> Result { let mut random = rand::thread_rng(); let upstream = self.upstreams.iter().choose(&mut random).unwrap(); - let result = match self.context.cache.lookup(qname, qtype) { + let mut result = match self.context.cache.lookup(qname, qtype) { None => { if is_url(upstream) { if let Some(client) = &self.context.doh_client { @@ -105,6 +105,17 @@ impl DnsResolver for ForwardingDnsResolver { self.context.cache.store(&result.answers)?; + // Fix domain names in answers to match original query case (DNS 0x20 may have randomized them) + let qname_lower = qname.to_lowercase(); + for answer in &mut result.answers { + if let Some(domain) = answer.get_domain() { + // Only fix if it matches the query (case-insensitive) + if domain.to_lowercase() == qname_lower { + answer.set_domain(qname.to_string()); + } + } + } + Ok(result) } } @@ -169,7 +180,18 @@ impl DnsResolver for RecursiveDnsResolver { let _ = self.context.cache.store(&response.answers); let _ = self.context.cache.store(&response.authorities); let _ = self.context.cache.store(&response.resources); - return Ok(response); + + // Fix domain names in answers to match original query case + let qname_lower = qname.to_lowercase(); + let mut fixed_response = response; + for answer in &mut fixed_response.answers { + if let Some(domain) = answer.get_domain() { + if domain.to_lowercase() == qname_lower { + answer.set_domain(qname.to_string()); + } + } + } + return Ok(fixed_response); } if response.header.rescode == ResultCode::NXDOMAIN { @@ -194,7 +216,19 @@ impl DnsResolver for RecursiveDnsResolver { // If not, we'll have to resolve the ip of a NS record let new_ns_name = match response.get_unresolved_ns(qname) { Some(x) => x, - None => return Ok(response) + None => { + // Fix domain names before returning + let qname_lower = qname.to_lowercase(); + let mut fixed_response = response; + for answer in &mut fixed_response.answers { + if let Some(domain) = answer.get_domain() { + if domain.to_lowercase() == qname_lower { + answer.set_domain(qname.to_string()); + } + } + } + return Ok(fixed_response); + } }; // Recursively resolve the NS @@ -204,7 +238,17 @@ impl DnsResolver for RecursiveDnsResolver { if let Some(new_ns) = recursive_response.get_random_a() { ns = new_ns.clone(); } else { - return Ok(response); + // Fix domain names before returning + let qname_lower = qname.to_lowercase(); + let mut fixed_response = response; + for answer in &mut fixed_response.answers { + if let Some(domain) = answer.get_domain() { + if domain.to_lowercase() == qname_lower { + answer.set_domain(qname.to_string()); + } + } + } + return Ok(fixed_response); } } } diff --git a/src/settings.rs b/src/settings.rs index bae75e1..349019f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -103,8 +103,8 @@ pub struct Dns { impl Default for Dns { fn default() -> Self { Dns { - listen: String::from("127.0.0.1:53"), - threads: 20, + listen: default_listen_dns(), + threads: 10, forwarders: vec![String::from("94.140.14.14:53"), String::from("94.140.15.15:53")], bootstraps: default_dns_bootstraps(), hosts: Vec::new(), @@ -136,7 +136,7 @@ pub struct Net { impl Default for Net { fn default() -> Self { Net { - peers: vec![String::from("test-ip4.alfis.name:4244"), String::from("test-ip6.alfis.name:4244")], + peers: vec![String::from("peer-v4.alfis.name:4244"), String::from("peer-v6.alfis.name:4244")], listen: String::from("[::]:4244"), public: true, yggdrasil_only: false @@ -149,11 +149,11 @@ fn default_listen() -> String { } fn default_listen_dns() -> String { - String::from("0.0.0.0:53") + String::from("127.0.0.3:53") } fn default_threads() -> usize { - 100 + 10 } fn default_check_blocks() -> u64 {