From 673c34cf5ac04d7061d283b02157ea3671dbd86e Mon Sep 17 00:00:00 2001 From: "sijie.sun" Date: Wed, 19 Feb 2025 23:30:10 +0800 Subject: [PATCH] http redirector --- Cargo.lock | 163 +++++-------- easytier/Cargo.toml | 8 +- easytier/src/connector/http_connector.rs | 298 +++++++++++++++++++++++ easytier/src/connector/mod.rs | 8 +- easytier/src/tunnel/mod.rs | 14 +- 5 files changed, 379 insertions(+), 112 deletions(-) create mode 100644 easytier/src/connector/http_connector.rs diff --git a/Cargo.lock b/Cargo.lock index c4ec1a0..c6b7271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,12 +664,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bigdecimal" version = "0.4.6" @@ -802,9 +796,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" dependencies = [ "borsh-derive", "cfg_aliases", @@ -812,16 +806,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.87", - "syn_derive", ] [[package]] @@ -1165,9 +1158,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", "clap_derive", @@ -1175,9 +1168,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstream", "anstyle", @@ -1185,14 +1178,14 @@ dependencies = [ "strsim", "terminal_size", "unicase", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1202,9 +1195,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clipboard-win" @@ -1888,6 +1881,7 @@ dependencies = [ "git-version", "globwalk", "http", + "http_req", "humansize", "kcp-sys", "machine-uid", @@ -3056,6 +3050,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http_req" +version = "0.13.1" +source = "git+https://github.com/EasyTier/http_req.git#e6d9e93c43c940f56f45e91b0923bcea53a718ea" +dependencies = [ + "base64 0.22.1", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "unicase", + "webpki", + "webpki-roots", + "zeroize", +] + [[package]] name = "httparse" version = "1.9.4" @@ -3686,39 +3695,6 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "logos" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-codegen" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" -dependencies = [ - "beef", - "fnv", - "lazy_static", - "proc-macro2", - "quote", - "regex-syntax 0.8.4", - "syn 2.0.87", -] - -[[package]] -name = "logos-derive" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" -dependencies = [ - "logos-codegen", -] - [[package]] name = "loom" version = "0.5.6" @@ -4603,15 +4579,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-float" version = "3.9.2" @@ -4725,7 +4692,7 @@ checksum = "c7419ad52a7de9b60d33e11085a0fe3df1fbd5926aa3f93d3dd53afbc9e86725" dependencies = [ "bytecount", "fnv", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] @@ -5396,14 +5363,10 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e92b959d24e05a3e2da1d0beb55b48bc8a97059b8336ea617780bd6addbbfb5a" dependencies = [ - "base64 0.22.1", - "logos", "once_cell", "prost", "prost-reflect-derive", "prost-types", - "serde", - "serde-value", ] [[package]] @@ -6080,19 +6043,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-platform-verifier" @@ -6278,9 +6240,9 @@ dependencies = [ [[package]] name = "sea-orm-cli" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aefbd960c9ed7b2dfbab97b11890f5d8c314ad6e2f68c7b36c73ea0967fcc25" +checksum = "0646647444d3a0366e30f26ff39f1656cc062b3dbf1f2e3d70cd9dc244b62cf7" dependencies = [ "chrono", "clap", @@ -6333,7 +6295,7 @@ dependencies = [ "bigdecimal", "chrono", "inherent", - "ordered-float 3.9.2", + "ordered-float", "rust_decimal", "sea-query-derive", "serde_json", @@ -6473,16 +6435,6 @@ dependencies = [ "typeid", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float 2.10.1", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.207" @@ -7231,18 +7183,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "sync_wrapper" version = "1.0.1" @@ -7762,12 +7702,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -8467,12 +8407,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" @@ -8513,6 +8450,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -8821,6 +8764,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.26.3" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index f695415..429002b 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -124,7 +124,7 @@ serde = { version = "1.0", features = ["derive"] } pnet = { version = "0.35.0", features = ["serde"] } serde_json = "1" -clap = { version = "4.4.8", features = [ +clap = { version = "4.5.30", features = [ "string", "unicode", "derive", @@ -182,14 +182,12 @@ async-compression = { version = "0.4.17", default-features = false, features = [ kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" } -prost-reflect = { version = "0.14.5", features = [ - "serde", +prost-reflect = { version = "0.14.5", default-features = false, features = [ "derive", - "text-format" ] } # for http connector -# reqwest = { version = "0.12.12", default-features = false, features = ["rustls-tls"] } +http_req = { git = "https://github.com/EasyTier/http_req.git", default-features = false, features = ["rust-tls"] } [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies] machine-uid = "0.5.3" diff --git a/easytier/src/connector/http_connector.rs b/easytier/src/connector/http_connector.rs new file mode 100644 index 0000000..255b84c --- /dev/null +++ b/easytier/src/connector/http_connector.rs @@ -0,0 +1,298 @@ +use std::{ + net::SocketAddr, + pin::Pin, + sync::{Arc, RwLock}, +}; + +use anyhow::Context; +use http_req::request::{RedirectPolicy, Request}; +use rand::seq::SliceRandom as _; + +use crate::{ + common::{error::Error, global_ctx::ArcGlobalCtx}, + tunnel::{IpVersion, Tunnel, TunnelConnector, TunnelError, ZCPacketSink, ZCPacketStream}, +}; + +use crate::proto::common::TunnelInfo; + +use super::create_connector_by_url; + +pub struct TunnelWithInfo { + inner: Box, + info: TunnelInfo, +} + +impl TunnelWithInfo { + pub fn new(inner: Box, info: TunnelInfo) -> Self { + Self { inner, info } + } +} + +impl Tunnel for TunnelWithInfo { + fn split(&self) -> (Pin>, Pin>) { + self.inner.split() + } + + fn info(&self) -> Option { + Some(self.info.clone()) + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +enum HttpRedirectType { + Unknown, + // redirected url is in the path of new url + RedirectToQuery, + // redirected url is the entire new url + RedirectToUrl, + // redirected url is in the body of response + BodyUrls, +} + +#[derive(Debug)] +pub struct HttpTunnelConnector { + addr: url::Url, + bind_addrs: Vec, + ip_version: IpVersion, + global_ctx: ArcGlobalCtx, + redirect_type: HttpRedirectType, +} + +impl HttpTunnelConnector { + pub fn new(addr: url::Url, global_ctx: ArcGlobalCtx) -> Self { + Self { + addr, + bind_addrs: Vec::new(), + ip_version: IpVersion::Both, + global_ctx, + redirect_type: HttpRedirectType::Unknown, + } + } + + #[tracing::instrument(ret)] + async fn handle_302_redirect( + &mut self, + new_url: url::Url, + ) -> Result, Error> { + // the url should be in following format: + // 1: http(s)://easytier.cn/?url=tcp://10.147.22.22:11010 (scheme is http, domain is ignored, path is splitted into proto type and addr) + // 2: tcp://10.137.22.22:11010 (scheme is protocol type, the url is used to construct a connector directly) + tracing::info!("redirect to {}", new_url); + let url = url::Url::parse(new_url.as_str()) + .with_context(|| format!("parsing redirect url failed. url: {}", new_url))?; + if url.scheme() == "http" || url.scheme() == "https" { + let mut query = new_url + .query_pairs() + .filter_map(|x| url::Url::parse(&x.1).ok()) + .collect::>(); + query.shuffle(&mut rand::thread_rng()); + if query.is_empty() { + return Err(Error::InvalidUrl(format!( + "no valid connector url found in url: url: {}", + url + ))); + } + tracing::info!("try to create connector by url: {}", query[0]); + self.redirect_type = HttpRedirectType::RedirectToQuery; + return create_connector_by_url(&query[0].to_string(), &self.global_ctx).await; + } else { + self.redirect_type = HttpRedirectType::RedirectToUrl; + return create_connector_by_url(new_url.as_str(), &self.global_ctx).await; + } + } + + #[tracing::instrument] + async fn handle_200_success( + &mut self, + body: &String, + ) -> Result, Error> { + // resp body should be line of connector urls, like: + // tcp://10.1.1.1:11010 + // udp://10.1.1.1:11010 + let mut lines = body + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()) + .collect::>(); + + tracing::info!("get {} lines of connector urls", lines.len()); + + // shuffle the lines and pick the usable one + lines.shuffle(&mut rand::thread_rng()); + + for line in lines { + let url = url::Url::parse(line); + if url.is_err() { + tracing::warn!("invalid url: {}, skip it", line); + continue; + } + self.redirect_type = HttpRedirectType::BodyUrls; + return create_connector_by_url(line, &self.global_ctx).await; + } + + Err(Error::InvalidUrl( + "no valid connector url found".to_string(), + )) + } + + #[tracing::instrument(ret)] + pub async fn get_redirected_connector( + &mut self, + original_url: &str, + ) -> Result, Error> { + self.redirect_type = HttpRedirectType::Unknown; + tracing::info!("get_redirected_url: {}", original_url); + // Container for body of a response. + let body = Arc::new(RwLock::new(Vec::new())); + + let original_url_clone = original_url.to_string(); + let body_clone = body.clone(); + let res = tokio::task::spawn_blocking(move || { + let uri = http_req::uri::Uri::try_from(original_url_clone.as_ref()) + .with_context(|| format!("parsing url failed. url: {}", original_url_clone))?; + + tracing::info!("sending http request to {}", uri); + + Request::new(&uri) + .redirect_policy(RedirectPolicy::Limit(0)) + .timeout(std::time::Duration::from_secs(5)) + .send(&mut *body_clone.write().unwrap()) + .with_context(|| format!("sending http request failed. url: {}", uri)) + }) + .await + .map_err(|e| Error::InvalidUrl(format!("task join error: {}", e)))??; + + let body = String::from_utf8_lossy(&body.read().unwrap()).to_string(); + + if res.status_code().is_redirect() { + let redirect_url = res + .headers() + .get("Location") + .ok_or_else(|| Error::InvalidUrl("no redirect address found".to_string()))?; + let new_url = url::Url::parse(redirect_url.as_str()) + .with_context(|| format!("parsing redirect url failed. url: {}", redirect_url))?; + return self.handle_302_redirect(new_url).await; + } else if res.status_code().is_success() { + return self.handle_200_success(&body).await; + } else { + return Err(Error::InvalidUrl(format!( + "unexpected response, resp: {:?}, body: {}", + res, body, + ))); + } + } +} + +#[async_trait::async_trait] +impl super::TunnelConnector for HttpTunnelConnector { + async fn connect(&mut self) -> Result, TunnelError> { + let mut conn = self + .get_redirected_connector(self.addr.to_string().as_str()) + .await + .with_context(|| "get redirected url failed")?; + conn.set_ip_version(self.ip_version); + let t = conn.connect().await?; + let info = t.info().unwrap_or_default(); + Ok(Box::new(TunnelWithInfo::new( + t, + TunnelInfo { + local_addr: info.local_addr.clone(), + remote_addr: Some(self.addr.clone().into()), + tunnel_type: format!( + "{:?}-{}", + self.redirect_type, + info.remote_addr.unwrap_or_default() + ), + }, + ))) + } + + fn remote_url(&self) -> url::Url { + self.addr.clone() + } + + fn set_bind_addrs(&mut self, addrs: Vec) { + self.bind_addrs = addrs; + } + + fn set_ip_version(&mut self, ip_version: IpVersion) { + self.ip_version = ip_version; + } +} + +#[cfg(test)] +mod tests { + use tokio::{io::AsyncWriteExt as _, net::TcpListener}; + + use crate::{ + common::global_ctx::tests::get_mock_global_ctx, + tunnel::{tcp::TcpTunnelListener, TunnelConnector, TunnelListener}, + }; + + use super::*; + + async fn run_http_redirect_server(port: u16, test_type: HttpRedirectType) -> Result<(), Error> { + let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?; + let (mut stream, _) = listener.accept().await?; + + match test_type { + HttpRedirectType::RedirectToQuery => { + let resp = "HTTP/1.1 301 Moved Permanently\r\nLocation: http://test.com/?url=tcp://127.0.0.1:25888\r\n\r\n"; + stream.write_all(resp.as_bytes()).await?; + } + HttpRedirectType::RedirectToUrl => { + let resp = + "HTTP/1.1 301 Moved Permanently\r\nLocation: tcp://127.0.0.1:25888\r\n\r\n"; + stream.write_all(resp.as_bytes()).await?; + } + HttpRedirectType::BodyUrls => { + let resp = "HTTP/1.1 200 OK\r\n\r\ntcp://127.0.0.1:25888"; + stream.write_all(resp.as_bytes()).await?; + } + HttpRedirectType::Unknown => { + panic!("unexpected test type"); + } + } + + Ok(()) + } + + #[rstest::rstest] + #[serial_test::serial(http_redirect_test)] + #[tokio::test] + async fn http_redirect_test( + // 1. 301 redirect + // 2. 200 success with valid connector urls + #[values( + HttpRedirectType::RedirectToQuery, + HttpRedirectType::RedirectToUrl, + HttpRedirectType::BodyUrls + )] + test_type: HttpRedirectType, + ) { + let http_task = tokio::spawn(run_http_redirect_server(35888, test_type)); + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + let test_url: url::Url = "http://127.0.0.1:35888".parse().unwrap(); + let global_ctx = get_mock_global_ctx(); + let mut flags = global_ctx.config.get_flags(); + flags.bind_device = false; + global_ctx.config.set_flags(flags); + let mut connector = HttpTunnelConnector::new(test_url.clone(), global_ctx.clone()); + + let mut listener = TcpTunnelListener::new("tcp://0.0.0.0:25888".parse().unwrap()); + listener.listen().await.unwrap(); + + let task = tokio::spawn(async move { + let _conn = listener.accept().await.unwrap(); + }); + + let t = connector.connect().await.unwrap(); + assert_eq!(connector.redirect_type, test_type); + let info = t.info().unwrap(); + let remote_addr = info.remote_addr.unwrap(); + assert_eq!(remote_addr, test_url.into()); + + tokio::join!(task).0.unwrap(); + tokio::join!(http_task).0.unwrap().unwrap(); + } +} diff --git a/easytier/src/connector/mod.rs b/easytier/src/connector/mod.rs index 0ec5f9c..e92029d 100644 --- a/easytier/src/connector/mod.rs +++ b/easytier/src/connector/mod.rs @@ -3,6 +3,8 @@ use std::{ sync::Arc, }; +use http_connector::HttpTunnelConnector; + #[cfg(feature = "quic")] use crate::tunnel::quic::QUICTunnelConnector; #[cfg(feature = "wireguard")] @@ -19,7 +21,7 @@ pub mod direct; pub mod manual; pub mod udp_hole_punch; -mod http_connector; +pub mod http_connector; async fn set_bind_addr_for_peer_connector( connector: &mut (impl TunnelConnector + ?Sized), @@ -81,6 +83,10 @@ pub async fn create_connector_by_url( } return Ok(Box::new(connector)); } + "http" | "https" => { + let connector = HttpTunnelConnector::new(url, global_ctx.clone()); + return Ok(Box::new(connector)); + } "ring" => { check_scheme_and_get_socket_addr::(&url, "ring")?; let connector = RingTunnelConnector::new(url); diff --git a/easytier/src/tunnel/mod.rs b/easytier/src/tunnel/mod.rs index eb32131..615feff 100644 --- a/easytier/src/tunnel/mod.rs +++ b/easytier/src/tunnel/mod.rs @@ -201,9 +201,21 @@ where Ok(T::from_url(url.clone(), IpVersion::Both)?) } +fn default_port(scheme: &str) -> Option { + match scheme { + "tcp" => Some(11010), + "udp" => Some(11010), + "ws" => Some(11011), + "wss" => Some(11012), + "quic" => Some(11012), + "wg" => Some(11011), + _ => None, + } +} + impl FromUrl for SocketAddr { fn from_url(url: url::Url, ip_version: IpVersion) -> Result { - let addrs = url.socket_addrs(|| None)?; + let addrs = url.socket_addrs(|| default_port(url.scheme()))?; tracing::debug!(?addrs, ?ip_version, ?url, "convert url to socket addrs"); let addrs = addrs .into_iter()