Skip to content

Commit

Permalink
feat: add padding
Browse files Browse the repository at this point in the history
  • Loading branch information
cyejing committed Mar 15, 2024
1 parent 65d94d0 commit 5bac07d
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 25 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "shuttle"
version = "0.6.5"
version = "0.6.6"
edition = "2021"
publish = false

Expand Down Expand Up @@ -67,3 +67,6 @@ futures= {workspace = true}

[dev-dependencies]
reqwest = { version = "0.11", default-features=false, features = ["socks","rustls-tls"] }

[patch.crates-io]
socks5-proto = {git="https://github.com/cyejing/socks5-server.git"}
2 changes: 1 addition & 1 deletion examples/shuttlec-proxy.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
run_type: proxy
proxy_mode: trojan # trojan,direct,websocket
proxy_addr: 0.0.0.0:4080
proxy_addr: 0.0.0.0:1082
ssl_enable: true
invalid_certs: true
remote_addr: 127.0.0.1:4845
Expand Down
1 change: 1 addition & 0 deletions shuttle-station/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ bytes = {workspace = true}
socks5-proto = {workspace = true}

async-trait = "0.1"
rand = "0.8.4"

25 changes: 20 additions & 5 deletions shuttle-station/src/dial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::MaybeTlsStream;

use crate::proto::padding::Padding;
use crate::proto::trojan;
use crate::tls::make_server_name;
use crate::tls::make_tls_connector;
Expand All @@ -28,6 +29,7 @@ pub struct TrojanDial {
remote_addr: String,
hash: String,
invalid_certs: bool,
padding: bool,
}
#[derive(Debug)]
pub struct WebSocketDial {
Expand All @@ -36,11 +38,12 @@ pub struct WebSocketDial {
}

impl TrojanDial {
pub fn new(remote_addr: String, hash: String, invalid_certs: bool) -> Self {
pub fn new(remote_addr: String, hash: String, invalid_certs: bool, padding: bool) -> Self {
Self {
remote_addr,
hash,
invalid_certs,
padding,
}
}
}
Expand Down Expand Up @@ -77,8 +80,14 @@ impl Dial<TcpStream> for TrojanDial {
.await
.context(format!("Trojan can't connect remote {}", remote_addr))?;

let req = trojan::Request::new(self.hash.clone(), Command::Connect, addr);
req.write_to(&mut remote_ts).await?;
if self.padding {
let req = trojan::Request::new(self.hash.clone(), Command::Padding, addr);
req.write_to(&mut remote_ts).await?;
Padding::read_from(&mut remote_ts).await?;
} else {
let req = trojan::Request::new(self.hash.clone(), Command::Connect, addr);
req.write_to(&mut remote_ts).await?;
};

Ok(remote_ts)
}
Expand All @@ -98,8 +107,14 @@ impl Dial<TlsStream<TcpStream>> for TrojanDial {
.await
.context("Trojan can't connect tls")?;

let req = trojan::Request::new(self.hash.clone(), Command::Connect, addr);
req.write_to(&mut remote_ts_ssl).await?;
if self.padding {
let req = trojan::Request::new(self.hash.clone(), Command::Padding, addr);
req.write_to(&mut remote_ts_ssl).await?;
Padding::read_from(&mut remote_ts_ssl).await?;
} else {
let req = trojan::Request::new(self.hash.clone(), Command::Connect, addr);
req.write_to(&mut remote_ts_ssl).await?;
}

Ok(remote_ts_ssl)
}
Expand Down
1 change: 1 addition & 0 deletions shuttle-station/src/proto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod http_connect;
pub mod padding;
pub mod trojan;
59 changes: 59 additions & 0 deletions shuttle-station/src/proto/padding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use anyhow::Context;
use bytes::{BufMut, BytesMut};
use rand::{Rng, RngCore};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

#[derive(Debug)]
pub struct Padding {
len: u16,
}

impl Padding {
pub async fn read_from<R>(stream: &mut R) -> anyhow::Result<Self>
where
R: AsyncRead + Unpin,
{
let len = stream.read_u16().await?;
let mut buf = vec![0; len as usize];
stream.read_exact(&mut buf).await?;
Ok(Padding { len })
}

pub async fn write_to<W>(&self, w: &mut W) -> anyhow::Result<()>
where
W: AsyncWrite + Unpin,
{
let mut buf = BytesMut::with_capacity(self.serialized_len());
self.write_to_buf(&mut buf);
w.write_all(&buf)
.await
.context("Padding Write buf failed")?;

Ok(())
}

pub fn write_to_buf<B: BufMut>(&self, buf: &mut B) {
let rand_buf = self.rand_buf();
buf.put_u16(self.len);
buf.put_slice(&rand_buf);
}

pub fn serialized_len(&self) -> usize {
2 + self.len as usize
}

pub fn rand_buf(&self) -> Vec<u8> {
let mut rng = rand::thread_rng();
let mut buf = vec![0; self.len as usize];
rng.fill_bytes(&mut buf);
buf
}
}

impl Default for Padding {
fn default() -> Self {
let mut rng = rand::thread_rng();
let len = rng.gen_range(100..3000);
Self { len }
}
}
15 changes: 15 additions & 0 deletions shuttle-station/src/proto/trojan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

use crate::{peekable::AsyncPeek, CRLF};

use super::padding::Padding;

/// Trojan request
///
/// ```plain
Expand Down Expand Up @@ -90,6 +92,12 @@ impl Request {

let _crlf = r.read_u16().await?;

if let Command::Padding = cmd {
let _padding = Padding::read_from(r)
.await
.context("trojan read padding failed")?;
}

Ok(Self::new(hash, cmd, addr))
}

Expand Down Expand Up @@ -173,6 +181,9 @@ impl Request {
buf.put_u8(u8::from(self.command));
self.write_to_buf_address(buf);
buf.put_slice(&CRLF);
if self.is_padding() {
Padding::default().write_to_buf(buf)
}
}
pub fn write_to_buf_address<B: BufMut>(&self, buf: &mut B) {
match &self.address {
Expand Down Expand Up @@ -200,4 +211,8 @@ impl Request {
pub fn serialized_len(&self) -> usize {
56 + 2 + 1 + self.address.serialized_len() + 2
}

pub fn is_padding(&self) -> bool {
Command::Padding == self.command
}
}
18 changes: 3 additions & 15 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::{sync::Arc, time::Duration};

use shuttle_station::{
dial::{Dial, DirectDial, TrojanDial, WebSocketDial},
dial::{DirectDial, TrojanDial, WebSocketDial},
proxy::ProxyConnection,
Address,
};
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::client::TlsStream;
Expand Down Expand Up @@ -68,6 +67,7 @@ async fn proxy_handle(cc: Arc<ClientConfig>, ts: TcpStream) {
cc.remote_addr.clone(),
cc.hash.clone(),
cc.invalid_certs,
cc.padding,
)),
)
.handle()
Expand All @@ -80,6 +80,7 @@ async fn proxy_handle(cc: Arc<ClientConfig>, ts: TcpStream) {
cc.remote_addr.clone(),
cc.hash.clone(),
cc.invalid_certs,
cc.padding,
)),
)
.handle()
Expand All @@ -95,16 +96,3 @@ async fn proxy_handle(cc: Arc<ClientConfig>, ts: TcpStream) {
}
};
}

#[allow(dead_code)]
fn websocket_heartbeat_open(remote_addr: String, hash: String) {
tokio::spawn(async move {
loop {
let ws_dial = WebSocketDial::new(remote_addr.clone(), hash.clone());
let address = Address::DomainAddress("api.shuttle.rs".as_bytes().to_vec(), 443);
let wss = ws_dial.dial(address).await.ok();
info!("heartbeat dial {remote_addr} [{}]", wss.is_some());
tokio::time::sleep(Duration::from_secs(30)).await
}
});
}
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct ClientConfig {
pub ssl_enable: bool,
#[serde(default = "default_false")]
pub invalid_certs: bool,
#[serde(default = "default_true")]
pub padding: bool,
#[serde(default)]
pub holes: Vec<Hole>,
}
Expand Down
5 changes: 5 additions & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{anyhow, Context};
use shuttle_station::dial::{Dial, DirectDial};
use shuttle_station::peekable::{AsyncPeek, PeekableStream};
use shuttle_station::proto::padding::Padding;
use shuttle_station::proto::{self, trojan};
use tracing::{info_span, Instrument};

Expand Down Expand Up @@ -124,6 +125,10 @@ where
.context("Trojan request read failed")?;
let addr = req.address.clone();

if req.is_padding() {
Padding::default().write_to(stream).await?;
}

debug!("Trojan start connect {addr}");

let mut remote_ts = DirectDial::default()
Expand Down

0 comments on commit 5bac07d

Please sign in to comment.