Add support for custom dns resolver on server

This commit is contained in:
Σrebe - Romain GERARD 2023-12-19 22:41:11 +01:00
parent d1de41646f
commit d456c67f19
No known key found for this signature in database
GPG key ID: 7A42B4B97E0332F4
8 changed files with 354 additions and 26 deletions

240
Cargo.lock generated
View file

@ -349,6 +349,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "data-encoding"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "deranged"
version = "0.3.10"
@ -369,6 +375,24 @@ dependencies = [
"subtle",
]
[[package]]
name = "enum-as-inner"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.41",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fast-socks5"
version = "0.9.2"
@ -535,6 +559,31 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "h2"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.11",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.4.1"
@ -553,6 +602,59 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hickory-proto"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf"
dependencies = [
"async-trait",
"bytes",
"cfg-if",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"h2",
"http 0.2.11",
"idna 0.4.0",
"ipnet",
"once_cell",
"rand",
"rustls",
"rustls-pemfile 1.0.4",
"thiserror",
"tinyvec",
"tokio",
"tokio-rustls",
"tracing",
"url",
]
[[package]]
name = "hickory-resolver"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
dependencies = [
"cfg-if",
"futures-util",
"hickory-proto",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot",
"rand",
"resolv-conf",
"rustls",
"smallvec",
"thiserror",
"tokio",
"tokio-rustls",
"tracing",
]
[[package]]
name = "hmac"
version = "0.12.1"
@ -562,6 +664,28 @@ dependencies = [
"digest",
]
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
"libc",
"match_cfg",
"winapi",
]
[[package]]
name = "http"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.0.0"
@ -580,7 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http",
"http 1.0.0",
]
[[package]]
@ -591,7 +715,7 @@ checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
dependencies = [
"bytes",
"futures-util",
"http",
"http 1.0.0",
"http-body",
"pin-project-lite",
]
@ -617,7 +741,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http 1.0.0",
"http-body",
"httparse",
"httpdate",
@ -636,7 +760,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http 1.0.0",
"http-body",
"hyper",
"pin-project-lite",
@ -653,6 +777,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.5.0"
@ -663,6 +797,34 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "ipconfig"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2",
"widestring",
"windows-sys 0.48.0",
"winreg",
]
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itoa"
version = "1.0.10"
@ -703,6 +865,12 @@ version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "lock_api"
version = "0.4.11"
@ -719,6 +887,21 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "matchers"
version = "0.1.0"
@ -914,6 +1097,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.33"
@ -1006,6 +1195,16 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "resolv-conf"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
dependencies = [
"hostname",
"quick-error",
]
[[package]]
name = "ring"
version = "0.17.7"
@ -1045,12 +1244,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pemfile 2.0.0",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
]
[[package]]
name = "rustls-pemfile"
version = "2.0.0"
@ -1481,6 +1689,7 @@ dependencies = [
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
@ -1620,7 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
"idna 0.5.0",
"percent-encoding",
]
@ -1734,6 +1943,12 @@ version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "winapi"
version = "0.3.9"
@ -1888,6 +2103,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wstunnel"
version = "8.3.0"
@ -1903,6 +2128,7 @@ dependencies = [
"fast-socks5",
"fastwebsockets",
"futures-util",
"hickory-resolver",
"http-body-util",
"hyper",
"hyper-util",
@ -1913,7 +2139,7 @@ dependencies = [
"parking_lot",
"pin-project",
"rustls-native-certs",
"rustls-pemfile",
"rustls-pemfile 2.0.0",
"scopeguard",
"serde",
"socket2",

View file

@ -17,6 +17,7 @@ clap = { version = "4.4.11", features = ["derive"] }
fast-socks5 = { version = "0.9.2", features = [] }
fastwebsockets = { version = "0.6.0", features = ["upgrade", "simd", "unstable-split"] }
futures-util = { version = "0.3.29" }
hickory-resolver = { version = "0.24.0", features = ["tokio", "dns-over-https-rustls", "dns-over-rustls"] }
hyper = { version = "1.0.1", features = ["client", "http1"] }
hyper-util = { version = "0.1.0", features = ["tokio"] }

32
src/dns.rs Normal file
View file

@ -0,0 +1,32 @@
use anyhow::Context;
use hickory_resolver::TokioAsyncResolver;
use std::net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6};
#[derive(Clone)]
pub enum DnsResolver {
System,
TrustDns(TokioAsyncResolver),
}
impl DnsResolver {
pub async fn lookup_host(&self, domain: &str, port: u16) -> anyhow::Result<Vec<SocketAddr>> {
let addrs: Vec<SocketAddr> = match self {
DnsResolver::System => tokio::net::lookup_host(format!("{}:{}", domain, port))
.await
.with_context(|| format!("cannot resolve domain: {}", domain))?
.collect(),
DnsResolver::TrustDns(dns_resolver) => dns_resolver
.lookup_ip(domain)
.await
.with_context(|| format!("cannot resolve domain: {}", domain))?
.into_iter()
.map(|ip| match ip {
IpAddr::V4(ip) => SocketAddr::V4(SocketAddrV4::new(ip, port)),
IpAddr::V6(ip) => SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)),
})
.collect(),
};
Ok(addrs)
}
}

View file

@ -1,3 +1,4 @@
mod dns;
mod embedded_certificate;
mod socks5;
mod stdio;
@ -9,13 +10,14 @@ mod udp;
use base64::Engine;
use clap::Parser;
use futures_util::{stream, TryStreamExt};
use hickory_resolver::config::{NameServerConfig, ResolverConfig, ResolverOpts};
use hyper::header::HOST;
use hyper::http::{HeaderName, HeaderValue};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::fmt::{Debug, Formatter};
use std::io::ErrorKind;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
@ -27,6 +29,7 @@ use tokio_rustls::rustls::{Certificate, PrivateKey, ServerName};
use tracing::{error, info, Level};
use crate::dns::DnsResolver;
use crate::tunnel::to_host_port;
use tracing_subscriber::EnvFilter;
use url::{Host, Url};
@ -157,6 +160,16 @@ struct Server {
#[arg(long, value_name = "DEST:PORT", verbatim_doc_comment)]
restrict_to: Option<Vec<String>>,
/// Dns resolver to use to lookup ips of domain name
/// This option is not going to work if you use transparent proxy
/// Can be specified multiple time
/// Example:
/// dns://1.1.1.1 for using udp
/// dns+https://1.1.1.1 for using dns over HTTPS
/// dns+tls://8.8.8.8 for using dns over TLS
#[arg(long, verbatim_doc_comment)]
dns_resolver: Option<Vec<Url>>,
/// Server will only accept connection from if this specific path prefix is used during websocket upgrade.
/// Useful if you specify in the client a custom path prefix and you want the server to only allow this one.
/// The path prefix act as a secret to authenticate clients
@ -445,6 +458,7 @@ pub struct WsServerConfig {
pub timeout_connect: Duration,
pub websocket_mask_frame: bool,
pub tls: Option<TlsServerConfig>,
pub dns_resolver: DnsResolver,
}
impl Debug for WsServerConfig {
@ -592,7 +606,14 @@ async fn main() {
let remote = tunnel.remote.clone();
let cfg = client_config.clone();
let connect_to_dest = |_| async {
tcp::connect(&remote.0, remote.1, cfg.socket_so_mark, cfg.timeout_connect).await
tcp::connect(
&remote.0,
remote.1,
cfg.socket_so_mark,
cfg.timeout_connect,
&DnsResolver::System,
)
.await
};
if let Err(err) =
@ -608,8 +629,9 @@ async fn main() {
tokio::spawn(async move {
let cfg = client_config.clone();
let remote = tunnel.remote.clone();
let connect_to_dest =
|_| async { udp::connect(&remote.0, remote.1, cfg.timeout_connect).await };
let connect_to_dest = |_| async {
udp::connect(&remote.0, remote.1, cfg.timeout_connect, &DnsResolver::System).await
};
if let Err(err) =
tunnel::client::run_reverse_tunnel(client_config, tunnel, connect_to_dest).await
@ -625,7 +647,9 @@ async fn main() {
let connect_to_dest = |remote: (Host, u16)| {
let so_mark = cfg.socket_so_mark;
let timeout = cfg.timeout_connect;
async move { tcp::connect(&remote.0, remote.1, so_mark, timeout).await }
async move {
tcp::connect(&remote.0, remote.1, so_mark, timeout, &DnsResolver::System).await
}
};
if let Err(err) =
@ -770,6 +794,29 @@ async fn main() {
None
};
let dns_resolver = match args.dns_resolver {
None => DnsResolver::System,
Some(resolvers) => {
let mut cfg = ResolverConfig::new();
for resolver in resolvers {
let (protocol, port) = match resolver.scheme() {
"dns" => (hickory_resolver::config::Protocol::Udp, resolver.port().unwrap_or(53)),
"dns+https" => (hickory_resolver::config::Protocol::Https, resolver.port().unwrap_or(853)),
"dns+tls" => (hickory_resolver::config::Protocol::Tls, resolver.port().unwrap_or(12)),
_ => panic!("invalid protocol for dns resolver"),
};
let sock = match resolver.host().unwrap() {
Host::Domain(_) => panic!("Dns resolver must be an ip address"),
Host::Ipv4(ip) => SocketAddr::V4(SocketAddrV4::new(ip, port)),
Host::Ipv6(ip) => SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)),
};
cfg.add_name_server(NameServerConfig::new(sock, protocol))
}
let opts = ResolverOpts::default();
DnsResolver::TrustDns(hickory_resolver::AsyncResolver::tokio(cfg, opts))
}
};
let server_config = WsServerConfig {
socket_so_mark: args.socket_so_mark,
bind: args.remote_addr.socket_addrs(|| Some(8080)).unwrap()[0],
@ -779,6 +826,7 @@ async fn main() {
timeout_connect: Duration::from_secs(10),
websocket_mask_frame: args.websocket_mask_frame,
tls: tls_config,
dns_resolver,
};
info!(

View file

@ -1,6 +1,7 @@
use anyhow::{anyhow, Context};
use std::{io, vec};
use crate::dns::DnsResolver;
use base64::Engine;
use bytes::BytesMut;
use log::warn;
@ -41,14 +42,15 @@ pub async fn connect(
port: u16,
so_mark: Option<u32>,
connect_timeout: Duration,
dns_resolver: &DnsResolver,
) -> Result<TcpStream, anyhow::Error> {
info!("Opening TCP connection to {}:{}", host, port);
let socket_addrs: Vec<SocketAddr> = match host {
Host::Domain(domain) => tokio::net::lookup_host(format!("{}:{}", domain, port))
Host::Domain(domain) => dns_resolver
.lookup_host(domain.as_str(), port)
.await
.with_context(|| format!("cannot resolve domain: {}", domain))?
.collect(),
.with_context(|| format!("cannot resolve domain: {}", domain))?,
Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))],
Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))],
};
@ -104,7 +106,7 @@ pub async fn connect_with_http_proxy(
let proxy_host = proxy.host().context("Cannot parse proxy host")?.to_owned();
let proxy_port = proxy.port_or_known_default().unwrap_or(80);
let mut socket = connect(&proxy_host, proxy_port, so_mark, connect_timeout).await?;
let mut socket = connect(&proxy_host, proxy_port, so_mark, connect_timeout, &DnsResolver::System).await?;
info!("Connected to http proxy {}:{}", proxy_host, proxy_port);
let authorization = if let Some((user, password)) = proxy.password().map(|p| (proxy.username(), p)) {

View file

@ -2,6 +2,7 @@ pub mod client;
mod io;
pub mod server;
use crate::dns::DnsResolver;
use crate::{tcp, tls, LocalProtocol, LocalToRemote, WsClientConfig};
use async_trait::async_trait;
use bb8::ManageConnection;
@ -126,7 +127,7 @@ impl ManageConnection for WsClientConfig {
let tcp_stream = if let Some(http_proxy) = &self.http_proxy {
tcp::connect_with_http_proxy(http_proxy, host, *port, so_mark, timeout).await?
} else {
tcp::connect(host, *port, so_mark, timeout).await?
tcp::connect(host, *port, so_mark, timeout, &DnsResolver::System).await?
};
match &self.tls {

View file

@ -67,7 +67,13 @@ async fn from_query(
match jwt.claims.p {
LocalProtocol::Udp { timeout, .. } => {
let host = Host::parse(&jwt.claims.r)?;
let cnx = udp::connect(&host, jwt.claims.rp, timeout.unwrap_or(Duration::from_secs(10))).await?;
let cnx = udp::connect(
&host,
jwt.claims.rp,
timeout.unwrap_or(Duration::from_secs(10)),
&server_config.dns_resolver,
)
.await?;
Ok((
LocalProtocol::Udp { timeout: None },
host,
@ -79,9 +85,15 @@ async fn from_query(
LocalProtocol::Tcp => {
let host = Host::parse(&jwt.claims.r)?;
let port = jwt.claims.rp;
let (rx, tx) = tcp::connect(&host, port, server_config.socket_so_mark, Duration::from_secs(10))
.await?
.into_split();
let (rx, tx) = tcp::connect(
&host,
port,
server_config.socket_so_mark,
Duration::from_secs(10),
&server_config.dns_resolver,
)
.await?
.into_split();
Ok((jwt.claims.p, host, port, Box::pin(rx), Box::pin(tx)))
}

View file

@ -18,6 +18,7 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::UdpSocket;
use tokio::sync::futures::Notified;
use crate::dns::DnsResolver;
use tokio::sync::Notify;
use tokio::time::{timeout, Interval};
use tracing::{debug, error, info};
@ -295,16 +296,21 @@ impl AsyncWrite for MyUdpSocket {
}
}
pub async fn connect(host: &Host<String>, port: u16, connect_timeout: Duration) -> anyhow::Result<MyUdpSocket> {
pub async fn connect(
host: &Host<String>,
port: u16,
connect_timeout: Duration,
dns_resolver: &DnsResolver,
) -> anyhow::Result<MyUdpSocket> {
info!("Opening UDP connection to {}:{}", host, port);
let socket_addrs: Vec<SocketAddr> = match host {
Host::Domain(domain) => timeout(connect_timeout, tokio::net::lookup_host(format!("{}:{}", domain, port)))
.await
.with_context(|| format!("cannot resolve domain: {}", domain))??
.collect(),
Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))],
Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))],
Host::Domain(domain) => dns_resolver
.lookup_host(domain.as_str(), port)
.await
.with_context(|| format!("cannot resolve domain: {}", domain))?,
};
let mut cnx = None;