feat: use custom DNS resolver for HTTP(S)

This commit is contained in:
link2xt
2024-07-20 12:59:51 +00:00
parent 2a605b93cd
commit 8f400dda85
7 changed files with 193 additions and 58 deletions

153
Cargo.lock generated
View File

@@ -430,6 +430,12 @@ dependencies = [
"tokio",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "attohttpc"
version = "0.24.1"
@@ -1430,12 +1436,12 @@ dependencies = [
"proptest",
"qrcodegen",
"quick-xml",
"quinn",
"quinn 0.10.2",
"quoted_printable",
"rand 0.8.5",
"ratelimit",
"regex",
"reqwest 0.11.27",
"reqwest 0.12.5",
"rusqlite",
"rust-hsluv",
"sanitize-filename",
@@ -2762,6 +2768,25 @@ dependencies = [
"tracing",
]
[[package]]
name = "h2"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "2.4.0"
@@ -3020,7 +3045,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
@@ -3043,6 +3068,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.5",
"http 1.1.0",
"http-body 1.0.0",
"httparse",
@@ -3070,32 +3096,36 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.2.0",
"hyper-util",
"rustls 0.22.4",
"rustls 0.23.10",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
"tokio-rustls 0.26.0",
"tower-service",
"webpki-roots 0.26.1",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"hyper 0.14.28",
"http-body-util",
"hyper 1.2.0",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
@@ -3299,7 +3329,7 @@ dependencies = [
"portable-atomic",
"postcard",
"quic-rpc",
"quinn",
"quinn 0.10.2",
"rand 0.7.3",
"rcgen 0.10.0",
"ring 0.16.20",
@@ -3403,7 +3433,7 @@ dependencies = [
"hyper-util",
"once_cell",
"prometheus-client",
"reqwest 0.12.4",
"reqwest 0.12.5",
"serde",
"struct_iterable",
"time 0.3.34",
@@ -3461,7 +3491,7 @@ dependencies = [
"rand 0.8.5",
"rand_core 0.6.4",
"rcgen 0.12.1",
"reqwest 0.12.4",
"reqwest 0.12.5",
"ring 0.17.8",
"rtnetlink",
"rustls 0.21.11",
@@ -4970,7 +5000,7 @@ dependencies = [
"flume",
"futures",
"pin-project",
"quinn",
"quinn 0.10.2",
"serde",
"tokio",
"tokio-serde",
@@ -5001,8 +5031,8 @@ checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75"
dependencies = [
"bytes",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"quinn-proto 0.10.6",
"quinn-udp 0.4.1",
"rustc-hash",
"rustls 0.21.11",
"thiserror",
@@ -5010,6 +5040,23 @@ dependencies = [
"tracing",
]
[[package]]
name = "quinn"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad"
dependencies = [
"bytes",
"pin-project-lite",
"quinn-proto 0.11.3",
"quinn-udp 0.5.2",
"rustc-hash",
"rustls 0.23.10",
"thiserror",
"tokio",
"tracing",
]
[[package]]
name = "quinn-proto"
version = "0.10.6"
@@ -5028,6 +5075,23 @@ dependencies = [
"tracing",
]
[[package]]
name = "quinn-proto"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe"
dependencies = [
"bytes",
"rand 0.8.5",
"ring 0.17.8",
"rustc-hash",
"rustls 0.23.10",
"slab",
"thiserror",
"tinyvec",
"tracing",
]
[[package]]
name = "quinn-udp"
version = "0.4.1"
@@ -5041,6 +5105,19 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "quinn-udp"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46"
dependencies = [
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
]
[[package]]
name = "quote"
version = "1.0.36"
@@ -5309,17 +5386,15 @@ dependencies = [
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.28",
"hyper-rustls 0.24.2",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
@@ -5331,7 +5406,6 @@ dependencies = [
"sync_wrapper 0.1.2",
"system-configuration 0.5.1",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.24.1",
"tower-service",
"url",
@@ -5344,36 +5418,43 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.12.4"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.4.5",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"hyper 1.2.0",
"hyper-rustls 0.26.0",
"hyper-rustls 0.27.2",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls 0.22.4",
"quinn 0.11.2",
"rustls 0.23.10",
"rustls-pemfile 2.1.2",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 0.1.2",
"sync_wrapper 1.0.0",
"system-configuration 0.5.1",
"tokio",
"tokio-rustls 0.25.0",
"tokio-native-tls",
"tokio-rustls 0.26.0",
"tower-service",
"url",
"wasm-bindgen",
@@ -5588,20 +5669,6 @@ dependencies = [
"sct",
]
[[package]]
name = "rustls"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
dependencies = [
"log",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.4",
"subtle",
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.10"
@@ -6670,11 +6737,11 @@ dependencies = [
[[package]]
name = "tokio-rustls"
version = "0.25.0"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.22.4",
"rustls 0.23.10",
"rustls-pki-types",
"tokio",
]

View File

@@ -81,7 +81,7 @@ quick-xml = "0.35"
quoted_printable = "0.5"
rand = { workspace = true }
regex = { workspace = true }
reqwest = { version = "0.11.27", features = ["json"] }
reqwest = { version = "0.12.5", features = ["json"] }
rusqlite = { workspace = true, features = ["sqlcipher"] }
rust-hsluv = "0.1"
sanitize-filename = { workspace = true }

View File

@@ -54,6 +54,7 @@ skip = [
{ name = "fastrand", version = "1.9.0" },
{ name = "futures-lite", version = "1.13.0" },
{ name = "getrandom", version = "<0.2" },
{ name = "h2", version = "0.3.26" },
{ name = "http-body", version = "0.4.6" },
{ name = "http", version = "0.2.12" },
{ name = "hyper-rustls", version = "0.24.2" },
@@ -78,7 +79,6 @@ skip = [
{ name = "ring", version = "0.16.20" },
{ name = "rustls-pemfile", version = "1.0.4" },
{ name = "rustls", version = "0.21.11" },
{ name = "rustls", version = "0.22.4" },
{ name = "rustls-webpki", version = "0.101.7" },
{ name = "sec1", version = "0.3.0" },
{ name = "sha2", version = "<0.10" },

View File

@@ -43,7 +43,7 @@ async fn lookup_host_with_timeout(
///
/// If `load_cache` is true, appends cached results not older than 30 days to the end
/// or entries from fallback cache if there are no cached addresses.
async fn lookup_host_with_cache(
pub(crate) async fn lookup_host_with_cache(
context: &Context,
hostname: &str,
port: u16,

View File

@@ -1,5 +1,6 @@
//! # HTTP module.
use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Result};
@@ -7,6 +8,7 @@ use mime::Mime;
use once_cell::sync::Lazy;
use crate::context::Context;
use crate::net::lookup_host_with_cache;
use crate::socks::Socks5Config;
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
@@ -60,8 +62,13 @@ pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
}
async fn read_url_inner(context: &Context, url: &str) -> Result<reqwest::Response> {
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let client = get_client(socks5_config)?;
// It is safe to use cached IP addresses
// for HTTPS URLs, but for HTTP URLs
// better resolve from scratch each time to prevent
// cache poisoning attacks from having lasting effects.
let load_cache = url.starts_with("https://");
let client = get_client(context, load_cache).await?;
let mut url = url.to_string();
// Follow up to 10 http-redirects
@@ -86,10 +93,57 @@ async fn read_url_inner(context: &Context, url: &str) -> Result<reqwest::Respons
Err(anyhow!("Followed 10 redirections"))
}
pub(crate) fn get_client(socks5_config: Option<Socks5Config>) -> Result<reqwest::Client> {
struct CustomResolver {
context: Context,
/// Whether to return cached results or not.
/// If resolver can be used for URLs
/// without TLS, e.g. HTTP URLs from HTML email,
/// this must be false. If TLS is used
/// and certificate hostnames are checked,
/// it is safe to load cache.
load_cache: bool,
}
impl CustomResolver {
fn new(context: Context, load_cache: bool) -> Self {
Self {
context,
load_cache,
}
}
}
impl reqwest::dns::Resolve for CustomResolver {
fn resolve(&self, hostname: reqwest::dns::Name) -> reqwest::dns::Resolving {
let context = self.context.clone();
let load_cache = self.load_cache;
Box::pin(async move {
let port = 443; // Actual port does not matter.
let socket_addrs =
lookup_host_with_cache(&context, hostname.as_str(), port, HTTP_TIMEOUT, load_cache)
.await;
match socket_addrs {
Ok(socket_addrs) => {
let addrs: reqwest::dns::Addrs = Box::new(socket_addrs.into_iter());
Ok(addrs)
}
Err(err) => Err(err.into()),
}
})
}
}
pub(crate) async fn get_client(context: &Context, load_cache: bool) -> Result<reqwest::Client> {
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let resolver = Arc::new(CustomResolver::new(context.clone(), load_cache));
let builder = reqwest::ClientBuilder::new()
.timeout(HTTP_TIMEOUT)
.add_root_certificate(LETSENCRYPT_ROOT.clone());
.add_root_certificate(LETSENCRYPT_ROOT.clone())
.dns_resolver(resolver);
let builder = if let Some(socks5_config) = socks5_config {
let proxy = reqwest::Proxy::all(socks5_config.to_url())?;

View File

@@ -10,7 +10,6 @@ use crate::config::Config;
use crate::context::Context;
use crate::provider;
use crate::provider::Oauth2Authorizer;
use crate::socks::Socks5Config;
use crate::tools::time;
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
@@ -159,8 +158,12 @@ pub(crate) async fn get_oauth2_access_token(
}
// ... and POST
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let client = crate::net::http::get_client(socks5_config)?;
// All OAuth URLs are hardcoded HTTPS URLs,
// so it is safe to load DNS cache.
let load_cache = true;
let client = crate::net::http::get_client(context, load_cache).await?;
let response: Response = match client.post(post_url).form(&post_param).send().await {
Ok(resp) => match resp.json().await {
@@ -290,8 +293,12 @@ impl Oauth2 {
// "verified_email": true,
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
// }
let socks5_config = Socks5Config::from_database(&context.sql).await.ok()?;
let client = match crate::net::http::get_client(socks5_config) {
// All OAuth URLs are hardcoded HTTPS URLs,
// so it is safe to load DNS cache.
let load_cache = true;
let client = match crate::net::http::get_client(context, load_cache).await {
Ok(cl) => cl,
Err(err) => {
warn!(context, "failed to get HTTP client: {}", err);

View File

@@ -20,7 +20,6 @@ use crate::events::EventType;
use crate::key::Fingerprint;
use crate::message::Message;
use crate::peerstate::Peerstate;
use crate::socks::Socks5Config;
use crate::token;
use crate::tools::validate_id;
use iroh_old as iroh;
@@ -590,8 +589,16 @@ struct CreateAccountErrorResponse {
#[allow(clippy::indexing_slicing)]
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let response = crate::net::http::get_client(socks5_config)?
if !url_str.starts_with(HTTPS_SCHEME) {
bail!("DCACCOUNT QR codes must use HTTPS scheme");
}
// As only HTTPS is used, it is safe to load DNS cache.
let load_cache = true;
let response = crate::net::http::get_client(context, load_cache)
.await?
.post(url_str)
.send()
.await?;