From 615671ab719efc8562d875bbb8c1215a9f2acdd3 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:22:29 +0800 Subject: [PATCH] multi path (#48) --- readme-cn.md | 3 ++ readme.md | 4 +++ src/client.rs | 3 +- src/config.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++---- src/server.rs | 10 ++++--- 5 files changed, 88 insertions(+), 10 deletions(-) diff --git a/readme-cn.md b/readme-cn.md index 616b0e1..1b56d1f 100644 --- a/readme-cn.md +++ b/readme-cn.md @@ -112,6 +112,9 @@ overtls -r client -c config.json 注意 `tunnel_path` 配置項,請務必改成你自己獨有的複雜字符串,否則 `GFW` 立馬拿你祭旗。 +> `tuunel_path` 選項現在可以是字符串或字符串數組,如 `["/secret-tunnel-path/", "/another-secret-tunnel-path/"]`。 +> Overtls 客戶端將選擇第一個使用。在服務端,它將用整個字符串數組来检查傳入請求. + > 爲方便測試,提供了 `disable_tls` 選項以具備停用 `TLS` 的能力;就是說,若該項存在且爲 `true` 時,本軟件將 `明文(plain text)` 傳輸流量;出於安全考慮,正式場合請勿使用。 本示例展示的是最少條目的配置文件,完整的配置文件可以參考 [config.json](config.json)。 diff --git a/readme.md b/readme.md index cf0e050..f1c6527 100644 --- a/readme.md +++ b/readme.md @@ -129,6 +129,10 @@ The `certfile` and `keyfile` are optional, and the software will become `https` Note the `tunnel_path` configuration, please make sure to change it to your own unique complex string, otherwise `GFW` will block you immediately. +> The `tunnel_path` option now can be a string or an array of strings, like `["/secret-tunnel-path/", "/another-secret-tunnel-path/"]`. +> Overtls client side will select the first one to use. +> In the server side, it will check the incoming request with the entire array of strings. + > For testing purposes, the `disable_tls` option is provided to have the ability to disable `TLS`; that is, if this option exists and is true, the software will transmit traffic in `plain text`; for security reasons, please do not use it on official occasions. This example shows the configuration file of the least entry, the complete configuration file can refer to [config.json](config.json). diff --git a/src/client.rs b/src/client.rs index 22c18b5..448fe3a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -245,7 +245,8 @@ pub(crate) async fn create_ws_stream( mut stream: S, ) -> Result> { let client = config.client.as_ref().ok_or("client not exist")?; - let tunnel_path = config.tunnel_path.trim_matches('/'); + let err = "tunnel path not exist"; + let tunnel_path = config.tunnel_path.extract().first().ok_or(err)?.trim_matches('/'); let b64_dst = dst_addr.as_ref().map(|dst_addr| addess_to_b64str(dst_addr, false)); diff --git a/src/config.rs b/src/config.rs index 9d979a0..bfd75d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,13 +14,80 @@ pub struct Config { pub remarks: Option, pub method: Option, pub password: Option, - pub tunnel_path: String, + pub tunnel_path: TunnelPath, #[serde(skip)] pub test_timeout_secs: u64, #[serde(skip)] pub is_server: bool, } +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum TunnelPath { + Single(String), + Multiple(Vec), +} + +impl std::fmt::Display for TunnelPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TunnelPath::Single(s) => write!(f, "{}", s), + TunnelPath::Multiple(v) => { + let mut s = String::new(); + for (i, item) in v.iter().enumerate() { + if i > 0 { + s.push(','); + } + s.push_str(item); + } + write!(f, "{}", s) + } + } + } +} + +impl Default for TunnelPath { + fn default() -> Self { + TunnelPath::Single("/tunnel/".to_string()) + } +} + +impl TunnelPath { + pub fn is_empty(&self) -> bool { + match self { + TunnelPath::Single(s) => s.is_empty(), + TunnelPath::Multiple(v) => v.is_empty(), + } + } + + pub fn standardize(&mut self) { + if self.is_empty() { + *self = TunnelPath::default(); + } + match self { + TunnelPath::Single(s) => { + *s = format!("/{}/", s.trim().trim_matches('/')); + } + TunnelPath::Multiple(v) => { + v.iter_mut().for_each(|s| { + *s = s.trim().trim_matches('/').to_string(); + if !s.is_empty() { + *s = format!("/{}/", s); + } + }); + v.retain(|s| !s.is_empty()); + } + } + } + + pub fn extract(&self) -> Vec<&str> { + match self { + TunnelPath::Single(s) => vec![s], + TunnelPath::Multiple(v) => v.iter().map(|s| s.as_str()).collect(), + } + } +} + #[derive(Clone, Serialize, Deserialize, Debug, Default)] pub struct Server { pub disable_tls: Option, @@ -70,7 +137,7 @@ impl Config { remarks: None, method: None, password: None, - tunnel_path: "/tunnel/".to_string(), + tunnel_path: TunnelPath::default(), server: None, client: None, test_timeout_secs: 5, @@ -177,9 +244,9 @@ impl Config { self.test_timeout_secs = 5; } if self.tunnel_path.is_empty() { - self.tunnel_path = "/tunnel/".to_string(); + self.tunnel_path = TunnelPath::default(); } else { - self.tunnel_path = format!("/{}/", self.tunnel_path.trim().trim_matches('/')); + self.tunnel_path.standardize(); } if let Some(server) = &mut self.server { @@ -238,7 +305,8 @@ impl Config { let remarks = crate::base64_encode(remarks.as_bytes(), engine); let domain = client.server_domain.as_ref().map_or("".to_string(), |d| d.clone()); let domain = crate::base64_encode(domain.as_bytes(), engine); - let tunnel_path = crate::base64_encode(self.tunnel_path.as_bytes(), engine); + let err = "tunnel_path is not set"; + let tunnel_path = crate::base64_encode(self.tunnel_path.extract().first().ok_or(err)?.as_bytes(), engine); let host = &client.server_host; let port = client.server_port; diff --git a/src/server.rs b/src/server.rs index 903c231..5f8094e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -128,7 +128,7 @@ async fn handle_incoming( return Err(Error::from("empty request")); } - if !check_uri_path(&buf, &config.tunnel_path)? { + if !check_uri_path(&buf, &config.tunnel_path.extract())? { return forward_traffic_wrapper(stream, &buf, &config).await; } @@ -158,14 +158,16 @@ where Ok(()) } -fn check_uri_path(buf: &[u8], path: &str) -> Result { +fn check_uri_path(buf: &[u8], path: &[&str]) -> Result { let mut headers = [httparse::EMPTY_HEADER; 512]; let mut req = httparse::Request::new(&mut headers); req.parse(buf)?; if let Some(p) = req.path { - if p == path { - return Ok(true); + for path in path { + if p == *path { + return Ok(true); + } } } Ok(false)