refactor(fakedns): rewrite fakedns storage to hashmap; add cargo docs; impl(fakedns): secure dns servers bootstrapping
This commit is contained in:
parent
927299baad
commit
7954859517
2 changed files with 136 additions and 35 deletions
|
|
@ -11,15 +11,21 @@ prost-build = "0.14.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arti-client = "0.40.0"
|
arti-client = "0.40.0"
|
||||||
|
hickory-resolver = { version = "0.26.3", features = ["dnssec-ring", "https-ring", "quic-ring", "serde", "tls-ring", "toml"] }
|
||||||
ipnet = "2.12.0"
|
ipnet = "2.12.0"
|
||||||
iptables = "0.6.0"
|
iptables = "0.6.0"
|
||||||
maxminddb = "0.27.3"
|
maxminddb = "0.27.3"
|
||||||
|
memmap2 = "0.9.10"
|
||||||
prost = "0.14.3"
|
prost = "0.14.3"
|
||||||
prost-types = "0.14.3"
|
prost-types = "0.14.3"
|
||||||
|
rand = "0.10.3"
|
||||||
|
regex = "1.13.1"
|
||||||
rtnetlink = "0.20.0"
|
rtnetlink = "0.20.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
smoltcp = "0.14.0"
|
||||||
tokio = { version = "1.50.0", features = ["full"] }
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
toml = { version = "1.0.6", features = ["serde"] }
|
toml = { version = "1.0.6", features = ["serde"] }
|
||||||
|
trie = "0.3.0"
|
||||||
tun = { version = "0.8.6", features = ["async", "tokio"] }
|
tun = { version = "0.8.6", features = ["async", "tokio"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
use hickory_resolver::{
|
use hickory_resolver::{
|
||||||
TokioResolver,
|
Resolver, TokioResolver,
|
||||||
config::{NameServerConfig, ResolverConfig},
|
config::{NameServerConfig, ResolverConfig},
|
||||||
net::runtime::TokioRuntimeProvider,
|
net::runtime::TokioRuntimeProvider,
|
||||||
proto::rr::IntoName,
|
proto::rr::IntoName,
|
||||||
};
|
};
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
struct FakeIPEntry {
|
pub type FakeIPStorage = HashMap<String, IpAddr>;
|
||||||
domain_name: String,
|
// domain_name: String,
|
||||||
address: IpAddr,
|
// address: IpAddr,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum IPStrategies {
|
pub enum IPStrategies {
|
||||||
|
|
@ -20,6 +21,7 @@ pub enum IPStrategies {
|
||||||
Any,
|
Any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
enum Protocols {
|
enum Protocols {
|
||||||
DoH,
|
DoH,
|
||||||
DoU,
|
DoU,
|
||||||
|
|
@ -27,19 +29,20 @@ enum Protocols {
|
||||||
DoTCP,
|
DoTCP,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FakeIPStorage {
|
pub struct FakeIPServer {
|
||||||
entries: Vec<FakeIPEntry>,
|
entries: FakeIPStorage,
|
||||||
resolver: Option<TokioResolver>,
|
pub resolver: Option<TokioResolver>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FakeIPOptions {
|
pub trait FakeIPOptions {
|
||||||
fn add_new(&mut self, domain: String) -> Ipv4Addr;
|
fn add(&mut self, domain: String) -> Ipv4Addr;
|
||||||
async fn add_real_ip(
|
async fn add_real_ip(
|
||||||
&mut self,
|
&mut self,
|
||||||
domain: String,
|
domain: String,
|
||||||
ip_type: IPStrategies,
|
ip_type: IPStrategies,
|
||||||
) -> Result<IpAddr, std::io::Error>;
|
) -> 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 {
|
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 network = u32::from(network);
|
||||||
let host_bits = 32 - prefix;
|
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)
|
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 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,
|
"tls" => Protocols::DoT,
|
||||||
"tcp" => Protocols::DoTCP,
|
"tcp" => Protocols::DoTCP,
|
||||||
"https" => Protocols::DoH,
|
"https" => Protocols::DoH,
|
||||||
"udp" => Protocols::DoU,
|
"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 {
|
// Bootstrapps secure DNS server via udp://8.8.8.8;
|
||||||
Protocols::DoT => {
|
// Logic tls://some-resolver.com -> udp://8.8.8.8:53 (guaranteed real answer, hardcoded) -> tls://123.123.123.123:853
|
||||||
NameServerConfig::tls(raw_protocol[1].to_ip().unwrap(), Arc::from(domain))
|
// same with https://
|
||||||
}
|
let secure_resolver_ip: Option<IpAddr> = if result == Protocols::DoH
|
||||||
Protocols::DoTCP => NameServerConfig::tcp(raw_protocol[1].to_ip().unwrap()),
|
|| result == Protocols::DoT
|
||||||
Protocols::DoU => NameServerConfig::udp(raw_protocol[1].to_ip().unwrap()),
|
{
|
||||||
_ => panic!("Wrong IP/domain of resolver"),
|
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]);
|
let config = ResolverConfig::from_parts(None, vec![], vec![ns]);
|
||||||
|
|
@ -87,18 +182,21 @@ impl FakeIPOptions for FakeIPStorage {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
/// Generates new IPv4 addr from network `198.18.0.0/15` and inserts it to FakeIPServer.entries
|
||||||
fn add_new(&mut self, domain: String) -> Ipv4Addr {
|
/// ```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 gen_ip = generate_ip_from(Ipv4Addr::new(198, 18, 0, 0), 15);
|
||||||
|
|
||||||
let entry = FakeIPEntry {
|
self.entries.insert(domain, IpAddr::V4(gen_ip));
|
||||||
domain_name: domain,
|
|
||||||
address: IpAddr::V4(gen_ip),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.entries.push(entry);
|
|
||||||
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(
|
async fn add_real_ip(
|
||||||
&mut self,
|
&mut self,
|
||||||
domain: String,
|
domain: String,
|
||||||
|
|
@ -126,10 +224,7 @@ impl FakeIPOptions for FakeIPStorage {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.entries.push(FakeIPEntry {
|
self.entries.insert(domain, ip);
|
||||||
domain_name: domain,
|
|
||||||
address: ip,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(ip)
|
Ok(ip)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue