Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi path #48

Merged
merged 4 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions readme-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)。
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
3 changes: 2 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ pub(crate) async fn create_ws_stream<S: AsyncRead + AsyncWrite + Unpin>(
mut stream: S,
) -> Result<WebSocketStream<S>> {
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));

Expand Down
78 changes: 73 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,80 @@ pub struct Config {
pub remarks: Option<String>,
pub method: Option<String>,
pub password: Option<String>,
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<String>),
}

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<bool>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down
10 changes: 6 additions & 4 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ async fn handle_incoming<S: AsyncRead + AsyncWrite + Unpin>(
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;
}

Expand Down Expand Up @@ -158,14 +158,16 @@ where
Ok(())
}

fn check_uri_path(buf: &[u8], path: &str) -> Result<bool> {
fn check_uri_path(buf: &[u8], path: &[&str]) -> Result<bool> {
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)
Expand Down
Loading