http redirector

This commit is contained in:
sijie.sun
2025-02-19 23:30:10 +08:00
committed by Sijie.Sun
parent 2050ed78d0
commit 673c34cf5a
5 changed files with 379 additions and 112 deletions

163
Cargo.lock generated
View File

@@ -664,12 +664,6 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]] [[package]]
name = "bigdecimal" name = "bigdecimal"
version = "0.4.6" version = "0.4.6"
@@ -802,9 +796,9 @@ dependencies = [
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "1.5.1" version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"cfg_aliases", "cfg_aliases",
@@ -812,16 +806,15 @@ dependencies = [
[[package]] [[package]]
name = "borsh-derive" name = "borsh-derive"
version = "1.5.1" version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"proc-macro-crate 3.1.0", "proc-macro-crate 3.1.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
"syn_derive",
] ]
[[package]] [[package]]
@@ -1165,9 +1158,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.15" version = "4.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -1175,9 +1168,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.15" version = "4.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -1185,14 +1178,14 @@ dependencies = [
"strsim", "strsim",
"terminal_size", "terminal_size",
"unicase", "unicase",
"unicode-width", "unicode-width 0.2.0",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.13" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -1202,9 +1195,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.2" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
@@ -1888,6 +1881,7 @@ dependencies = [
"git-version", "git-version",
"globwalk", "globwalk",
"http", "http",
"http_req",
"humansize", "humansize",
"kcp-sys", "kcp-sys",
"machine-uid", "machine-uid",
@@ -3056,6 +3050,21 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "httparse" name = "httparse"
version = "1.9.4" version = "1.9.4"
@@ -3686,39 +3695,6 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 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]] [[package]]
name = "loom" name = "loom"
version = "0.5.6" version = "0.5.6"
@@ -4603,15 +4579,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 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]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "3.9.2" version = "3.9.2"
@@ -4725,7 +4692,7 @@ checksum = "c7419ad52a7de9b60d33e11085a0fe3df1fbd5926aa3f93d3dd53afbc9e86725"
dependencies = [ dependencies = [
"bytecount", "bytecount",
"fnv", "fnv",
"unicode-width", "unicode-width 0.1.11",
] ]
[[package]] [[package]]
@@ -5396,14 +5363,10 @@ version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e92b959d24e05a3e2da1d0beb55b48bc8a97059b8336ea617780bd6addbbfb5a" checksum = "e92b959d24e05a3e2da1d0beb55b48bc8a97059b8336ea617780bd6addbbfb5a"
dependencies = [ dependencies = [
"base64 0.22.1",
"logos",
"once_cell", "once_cell",
"prost", "prost",
"prost-reflect-derive", "prost-reflect-derive",
"prost-types", "prost-types",
"serde",
"serde-value",
] ]
[[package]] [[package]]
@@ -6080,19 +6043,18 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pemfile" name = "rustls-pemfile"
version = "2.1.3" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [ dependencies = [
"base64 0.22.1",
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.8.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
[[package]] [[package]]
name = "rustls-platform-verifier" name = "rustls-platform-verifier"
@@ -6278,9 +6240,9 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm-cli" name = "sea-orm-cli"
version = "1.1.0" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aefbd960c9ed7b2dfbab97b11890f5d8c314ad6e2f68c7b36c73ea0967fcc25" checksum = "0646647444d3a0366e30f26ff39f1656cc062b3dbf1f2e3d70cd9dc244b62cf7"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@@ -6333,7 +6295,7 @@ dependencies = [
"bigdecimal", "bigdecimal",
"chrono", "chrono",
"inherent", "inherent",
"ordered-float 3.9.2", "ordered-float",
"rust_decimal", "rust_decimal",
"sea-query-derive", "sea-query-derive",
"serde_json", "serde_json",
@@ -6473,16 +6435,6 @@ dependencies = [
"typeid", "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]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.207" version = "1.0.207"
@@ -7231,18 +7183,6 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "sync_wrapper" name = "sync_wrapper"
version = "1.0.1" version = "1.0.1"
@@ -7762,12 +7702,12 @@ dependencies = [
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.3.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
dependencies = [ dependencies = [
"rustix", "rustix",
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -8467,12 +8407,9 @@ dependencies = [
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.7.0" version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
@@ -8513,6 +8450,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "unicode_categories" name = "unicode_categories"
version = "0.1.1" version = "0.1.1"
@@ -8821,6 +8764,16 @@ dependencies = [
"system-deps", "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]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.3" version = "0.26.3"

View File

@@ -124,7 +124,7 @@ serde = { version = "1.0", features = ["derive"] }
pnet = { version = "0.35.0", features = ["serde"] } pnet = { version = "0.35.0", features = ["serde"] }
serde_json = "1" serde_json = "1"
clap = { version = "4.4.8", features = [ clap = { version = "4.5.30", features = [
"string", "string",
"unicode", "unicode",
"derive", "derive",
@@ -182,14 +182,12 @@ async-compression = { version = "0.4.17", default-features = false, features = [
kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" } kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" }
prost-reflect = { version = "0.14.5", features = [ prost-reflect = { version = "0.14.5", default-features = false, features = [
"serde",
"derive", "derive",
"text-format"
] } ] }
# for http connector # 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] [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies]
machine-uid = "0.5.3" machine-uid = "0.5.3"

View File

@@ -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<dyn Tunnel>,
info: TunnelInfo,
}
impl TunnelWithInfo {
pub fn new(inner: Box<dyn Tunnel>, info: TunnelInfo) -> Self {
Self { inner, info }
}
}
impl Tunnel for TunnelWithInfo {
fn split(&self) -> (Pin<Box<dyn ZCPacketStream>>, Pin<Box<dyn ZCPacketSink>>) {
self.inner.split()
}
fn info(&self) -> Option<TunnelInfo> {
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<SocketAddr>,
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<Box<dyn TunnelConnector>, 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::<Vec<_>>();
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<Box<dyn TunnelConnector>, 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::<Vec<&str>>();
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<Box<dyn TunnelConnector>, 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<Box<dyn Tunnel>, 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<SocketAddr>) {
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();
}
}

View File

@@ -3,6 +3,8 @@ use std::{
sync::Arc, sync::Arc,
}; };
use http_connector::HttpTunnelConnector;
#[cfg(feature = "quic")] #[cfg(feature = "quic")]
use crate::tunnel::quic::QUICTunnelConnector; use crate::tunnel::quic::QUICTunnelConnector;
#[cfg(feature = "wireguard")] #[cfg(feature = "wireguard")]
@@ -19,7 +21,7 @@ pub mod direct;
pub mod manual; pub mod manual;
pub mod udp_hole_punch; pub mod udp_hole_punch;
mod http_connector; pub mod http_connector;
async fn set_bind_addr_for_peer_connector( async fn set_bind_addr_for_peer_connector(
connector: &mut (impl TunnelConnector + ?Sized), connector: &mut (impl TunnelConnector + ?Sized),
@@ -81,6 +83,10 @@ pub async fn create_connector_by_url(
} }
return Ok(Box::new(connector)); return Ok(Box::new(connector));
} }
"http" | "https" => {
let connector = HttpTunnelConnector::new(url, global_ctx.clone());
return Ok(Box::new(connector));
}
"ring" => { "ring" => {
check_scheme_and_get_socket_addr::<uuid::Uuid>(&url, "ring")?; check_scheme_and_get_socket_addr::<uuid::Uuid>(&url, "ring")?;
let connector = RingTunnelConnector::new(url); let connector = RingTunnelConnector::new(url);

View File

@@ -201,9 +201,21 @@ where
Ok(T::from_url(url.clone(), IpVersion::Both)?) Ok(T::from_url(url.clone(), IpVersion::Both)?)
} }
fn default_port(scheme: &str) -> Option<u16> {
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 { impl FromUrl for SocketAddr {
fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> { fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> {
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"); tracing::debug!(?addrs, ?ip_version, ?url, "convert url to socket addrs");
let addrs = addrs let addrs = addrs
.into_iter() .into_iter()