From 79177fc28be95dea6ae3c63fe6c01d35b1ce7293 Mon Sep 17 00:00:00 2001 From: Uncle Jack Date: Fri, 19 Jul 2024 18:55:48 +0330 Subject: [PATCH] add trojan outbound Signed-off-by: Uncle Jack --- config.schema.json | 4 ++ src/config.rs | 2 + src/proxy/mod.rs | 1 + src/proxy/trojan/mod.rs | 1 + src/proxy/trojan/outbound.rs | 107 +++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 src/proxy/trojan/outbound.rs diff --git a/config.schema.json b/config.schema.json index 6bd34e8..f825555 100644 --- a/config.schema.json +++ b/config.schema.json @@ -64,6 +64,10 @@ "format": "ip" } }, + "password": { + "default": "", + "type": "string" + }, "port": { "default": 0, "type": "integer", diff --git a/src/config.rs b/src/config.rs index 0866db6..e75848e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,6 +34,8 @@ pub struct Outbound { pub port: u16, #[serde(default)] pub uuid: Uuid, + #[serde(default)] + pub password: String, } #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs index 5c03396..531b3a3 100644 --- a/src/proxy/mod.rs +++ b/src/proxy/mod.rs @@ -113,6 +113,7 @@ async fn connect_outbound(ctx: RequestContext, outbound: Outbound) -> Result = match outbound.protocol { Protocol::Vless => Box::new(vless::outbound::VlessStream::new(ctx, outbound, socket)), + Protocol::Trojan => Box::new(trojan::outbound::TrojanStream::new(ctx, outbound, socket)), Protocol::RelayV1 => Box::new(relay::outbound::RelayStream::new( ctx, socket, diff --git a/src/proxy/trojan/mod.rs b/src/proxy/trojan/mod.rs index 7cc2efb..bde65dc 100644 --- a/src/proxy/trojan/mod.rs +++ b/src/proxy/trojan/mod.rs @@ -1,2 +1,3 @@ mod encoding; pub mod inbound; +pub mod outbound; diff --git a/src/proxy/trojan/outbound.rs b/src/proxy/trojan/outbound.rs new file mode 100644 index 0000000..1153b29 --- /dev/null +++ b/src/proxy/trojan/outbound.rs @@ -0,0 +1,107 @@ +use crate::common::encode_addr; +use crate::proxy::{Proxy, RequestContext}; + +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::config::Outbound; + +use async_trait::async_trait; +use bytes::{BufMut, BytesMut}; +use sha2::{Digest, Sha224}; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf}; +use worker::*; + +pub struct TrojanStream { + pub stream: Socket, + pub buffer: BytesMut, + pub outbound: Outbound, + context: RequestContext, + handshaked: bool, +} + +impl TrojanStream { + pub fn new(context: RequestContext, outbound: Outbound, stream: Socket) -> Self { + let buffer = BytesMut::new(); + + Self { + context, + outbound, + stream, + buffer, + handshaked: false, + } + } +} + +#[async_trait] +impl Proxy for TrojanStream { + async fn process(&mut self) -> Result<()> { + let crlf = [0xd, 0xa]; + + let mut cmd: Vec = vec![]; + + let password = { + let p = &crate::sha224!(&self.outbound.password)[..]; + crate::hex!(p) + }; + cmd.extend_from_slice(password.as_bytes()); + + cmd.extend_from_slice(&crlf); + cmd.extend_from_slice(&[ + 0x1, // TODO: udp + 0x1, // TODO: ipv6 & domain + ]); + + cmd.extend_from_slice(&encode_addr(&self.context.address)?); + cmd.extend_from_slice(&self.context.port.to_be_bytes()); + + cmd.extend_from_slice(&crlf); + + self.stream.write_all(&cmd).await?; + + Ok(()) + } +} + +impl AsyncRead for TrojanStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if self.buffer.len() > 0 { + let size = std::cmp::min(buf.remaining(), self.buffer.len()); + let data = self.buffer.split_to(size); + buf.put_slice(&data); + return Poll::Ready(Ok(())); + } + + match Pin::new(&mut self.stream).poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + self.buffer.put_slice(buf.filled()); + Poll::Ready(Ok(())) + } + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + } + } +} + +impl AsyncWrite for TrojanStream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.stream).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context) -> Poll> { + unimplemented!() + } +}