refactor(fakedns): rewrite fakedns storage to hashmap; add cargo docs; impl(fakedns): secure dns servers bootstrapping

This commit is contained in:
Namilsk 2026-09-28 18:45:28 +03:00
parent 927299baad
commit 7954859517
No known key found for this signature in database
GPG key ID: 2B2F0A4D870B4F9F
2 changed files with 136 additions and 35 deletions

View file

@ -1,17 +1,18 @@
use hickory_resolver::{
TokioResolver,
Resolver, TokioResolver,
config::{NameServerConfig, ResolverConfig},
net::runtime::TokioRuntimeProvider,
proto::rr::IntoName,
};
use rand::RngExt;
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
struct FakeIPEntry {
domain_name: String,
address: IpAddr,
}
pub type FakeIPStorage = HashMap<String, IpAddr>;
// domain_name: String,
// address: IpAddr,
#[derive(Debug, PartialEq, Eq)]
pub enum IPStrategies {
@ -20,6 +21,7 @@ pub enum IPStrategies {
Any,
}
#[derive(Debug, Eq, PartialEq)]
enum Protocols {
DoH,
DoU,
@ -27,19 +29,20 @@ enum Protocols {
DoTCP,
}
pub struct FakeIPStorage {
entries: Vec<FakeIPEntry>,
resolver: Option<TokioResolver>,
pub struct FakeIPServer {
entries: FakeIPStorage,
pub resolver: Option<TokioResolver>,
}
pub trait FakeIPOptions {
fn add_new(&mut self, domain: String) -> Ipv4Addr;
fn add(&mut self, domain: String) -> Ipv4Addr;
async fn add_real_ip(
&mut self,
domain: String,
ip_type: IPStrategies,
) -> Result<IpAddr, std::io::Error>;
fn init_resolver(&mut self, link: String, domain: String) -> Result<(), std::io::Error>;
async fn init_resolver(&mut self, link: String) -> Result<(), std::io::Error>;
fn get(&self, domain: &str) -> Result<IpAddr, std::io::Error>;
}
fn generate_ip_from(network: Ipv4Addr, prefix: u32) -> Ipv4Addr {
@ -47,34 +50,126 @@ fn generate_ip_from(network: Ipv4Addr, prefix: u32) -> Ipv4Addr {
let network = u32::from(network);
let host_bits = 32 - prefix;
let host = rng.random_range(0..(1u32 << host_bits));
let host = rng.random_range(1..(1u32 << host_bits));
Ipv4Addr::from(network | host)
}
impl FakeIPOptions for FakeIPStorage {
fn init_resolver(&mut self, link: String, domain: String) -> Result<(), std::io::Error> {
// !TODO #[must_use]:FakeIpServer::new() builder AND FakeIPServer::start(), which unnecessary starts
// DNS server itself
impl FakeIPOptions for FakeIPServer {
/// Just gets an FakeIP addr from FakeIPStorage.
/// ```rust
/// x = FakeIPServer::new(/* params */);
/// x.add("nosystemd.org") // returns IpAddr, but if it dropped you can call
/// x.get("nosystemd.org").unwrap() // -> IpAddr
/// ```
fn get(&self, domain: &str) -> Result<IpAddr, std::io::Error> {
self.entries.get(domain).copied().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("No FakeIP entry found for domain: {domain}"),
)
})
}
/// FakeIPServer function which init's integrated DNS resolver for
/// ```rust
/// let x: FakeIPServer = FakeIPServer::new( /* idk, params */ ).init_resolver("udp://8.8.8.8").add_real_ip("domain.com");
/// // for tls:// and https:// allowed ONLY domain.
/// x.add_new("nosystemd.org");
/// ```
/// Doesn't call it if you wouldnt use `.add_real_ip()` - which needed ONLY for `AlwaysReal`
/// FakeIPServer strategy;
///
/// P.S. FakeIPServer: AlwaysReal; Needed if you regularry uses `dnsutils` from your console
/// and dont like see Fake IP address. Or use strict antifrauded corporative software which
/// checking your DNS. (Like my ChatGPT app, before i stopped using xray core fakedns)
async fn init_resolver(&mut self, link: String) -> Result<(), std::io::Error> {
// just because i can
let server_string = link.to_lowercase();
let raw_protocol: Vec<&str> = server_string.split("://").collect();
// parses dns url (ONLY with protocol) like `tls://domain.com` or `https://dns.google.com` etc.
// to
// [0]: protocol like `tls`/`udp`/`tcp` e.g.
// [1]: domain of the server
// overrides /dns-query path
let (protocol, server) = server_string.split_once("://").ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Resolver URL must contain `protocol://`",
)
})?;
let result = match raw_protocol[0] {
// ts soooo shitty parsing
let result = match protocol {
"tls" => Protocols::DoT,
"tcp" => Protocols::DoTCP,
"https" => Protocols::DoH,
"udp" => Protocols::DoU,
_ => {
panic!("Wrong resolver protocol");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Wrong resolver protocol",
));
}
};
let ns: NameServerConfig = match result {
Protocols::DoT => {
NameServerConfig::tls(raw_protocol[1].to_ip().unwrap(), Arc::from(domain))
}
Protocols::DoTCP => NameServerConfig::tcp(raw_protocol[1].to_ip().unwrap()),
Protocols::DoU => NameServerConfig::udp(raw_protocol[1].to_ip().unwrap()),
_ => panic!("Wrong IP/domain of resolver"),
// Bootstrapps secure DNS server via udp://8.8.8.8;
// Logic tls://some-resolver.com -> udp://8.8.8.8:53 (guaranteed real answer, hardcoded) -> tls://123.123.123.123:853
// same with https://
let secure_resolver_ip: Option<IpAddr> = if result == Protocols::DoH
|| result == Protocols::DoT
{
let bootstrap = TokioResolver::builder_with_config(
ResolverConfig::from_parts(
None,
vec![],
vec![NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)))],
),
TokioRuntimeProvider::default(),
)
.build()
.map_err(|e| {
std::io::Error::other(format!("Failed to create bootstrap resolver: {e}"))
})?;
Some(
bootstrap
.lookup_ip(server)
.await
.map_err(|e| std::io::Error::other(format!("Failed to resolve {server}: {e}")))?
.iter()
.next()
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("No IP addresses found for {server}"),
)
})?,
)
} else {
None
};
let ip = secure_resolver_ip
.or_else(|| server.parse::<IpAddr>().ok())
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Resolver address is not an IP: {server}"),
)
})?;
let ns = match result {
Protocols::DoT => NameServerConfig::tls(ip, Arc::from(server.to_owned())),
Protocols::DoTCP => NameServerConfig::tcp(ip),
Protocols::DoU => NameServerConfig::udp(ip),
Protocols::DoH => NameServerConfig::https(
ip,
Arc::from(server.to_owned()),
Some(Arc::from("/dns-query")),
),
_ => unreachable!(),
};
let config = ResolverConfig::from_parts(None, vec![], vec![ns]);
@ -87,18 +182,21 @@ impl FakeIPOptions for FakeIPStorage {
Ok(())
}
fn add_new(&mut self, domain: String) -> Ipv4Addr {
/// Generates new IPv4 addr from network `198.18.0.0/15` and inserts it to FakeIPServer.entries
/// ```rust
/// let x = FakeIPServer::new( /* params */ );
/// x.add_new("nosystemd.org");
/// println!("Given IP: {}: {}", "nosystemd.org", x.entries.get("nosystemd.org"));
/// ```
fn add(&mut self, domain: String) -> Ipv4Addr {
let gen_ip = generate_ip_from(Ipv4Addr::new(198, 18, 0, 0), 15);
let entry = FakeIPEntry {
domain_name: domain,
address: IpAddr::V4(gen_ip),
};
self.entries.push(entry);
self.entries.insert(domain, IpAddr::V4(gen_ip));
gen_ip
}
/// Adds to FakeIPStorage REAL ip resolved by FakeIPServer.resolver; Configure it with calling
/// `x.init_resolver(/* "tls://example.com" || "https://dns.google.com/" etc. */).await`
/// Fuction will panic if you doesnt call `init_resolver` and using `add_real_ip`
async fn add_real_ip(
&mut self,
domain: String,
@ -126,10 +224,7 @@ impl FakeIPOptions for FakeIPStorage {
)
})?;
self.entries.push(FakeIPEntry {
domain_name: domain,
address: ip,
});
self.entries.insert(domain, ip);
Ok(ip)
}