naresh97 / trojan-rs

A Trojan proxy implementation written in Rust. Optimizing for size.
GNU General Public License v3.0
3 stars 0 forks source link

udp does not work #22

Open cattyhouse opened 5 months ago

cattyhouse commented 5 months ago

trojan protocol can handle udp traffic, but this software does not support UDP.

For example dig @1.1.1.1 example.com via socks5 proxy.

naresh97 commented 5 months ago

After browsing the Trojan docs, and not willing to look at the source code yet :D I'm unsure whether the UDP streams are forwarded over TCP? To me that would make the most sense, if the goal is to pretend to be a normal HTTPS connection, but I could be wrong

cattyhouse commented 5 months ago

i use this client daily, it is also written in rust, maybe you could take a look at how it does UDP, for your reference: https://github.com/p4gefau1t/trojan-r/tree/main/src/protocol/trojan

naresh97 commented 5 months ago

Thanks! I'll take a look.

A user on the forums has mentioned that UDP is implemented as UDP over TCP.

cattyhouse commented 5 months ago

the issue with p4gefau1t(who is also the author of trojan-go)'s trojan-r project is that:

  1. there is a tcp async write bug that may cause data lost, i fixed it by replacing write() with write_all() method where possible in the code
  2. the rustls version is a little bit old, hence the server does not support EC certs, needs a convert: openssl pkcs8 -topk8 -nocrypt -in ECC.key -out pkcs8.key for it to use, but i don't use it's server feature, caddy-trojan as a server is more convenient. but since rustls has many breaking changes since then, it is not easy to upgrade to the latest version.
  3. the author "disappeared" 3 years ago...

everything else is perfect, latency / performance / memory usage etc.

here is the patch i used on that project:

diff --git a/Cargo.toml b/Cargo.toml
index d6691b5..9ef02cb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "trojan-r"
 version = "0.1.0"
 authors = ["Page Fault <p4gefau1t@gmail.com>"]
-edition = "2018"
+edition = "2021"

 [dependencies]
 bytes = "1.0"
@@ -25,8 +25,8 @@ futures-util = "0.3"
 lto = true

 [features]
-default = ["full"]
+default = ["client"]
 client = []
 server = []
 forward = []
-full = ["client", "server", "forward"]
\ No newline at end of file
+full = ["client", "server", "forward"]
diff --git a/src/protocol/dokodemo/acceptor.rs b/src/protocol/dokodemo/acceptor.rs
index a0437ad..e0ef456 100644
--- a/src/protocol/dokodemo/acceptor.rs
+++ b/src/protocol/dokodemo/acceptor.rs
@@ -69,8 +69,7 @@ impl ProxyAcceptor for DokodemoAcceptor {
     type US = DokodemoUdpStream;

     async fn accept(&self) -> Result<AcceptResult<Self::TS, Self::US>> {
-        if !self.udp_spawned.load(Ordering::Relaxed) {
-            self.udp_spawned.store(true, Ordering::Relaxed);
+        if !self.udp_spawned.swap(true,Ordering::Relaxed) {
             let socket = Arc::new(UdpSocket::bind(self.tcp_listener.local_addr().unwrap()).await?);
             let udp_stream = DokodemoUdpStream {
                 inner: socket,
diff --git a/src/protocol/mux/mod.rs b/src/protocol/mux/mod.rs
index 882a42e..35c0e98 100644
--- a/src/protocol/mux/mod.rs
+++ b/src/protocol/mux/mod.rs
@@ -89,7 +89,7 @@ impl RequestHeader {
         cursor.put_u8(cmd);
         addr.write_to_buf(cursor);

-        w.write(&buf).await?;
+        w.write_all(&buf).await?;
         Ok(())
     }
 }
@@ -138,9 +138,9 @@ impl MuxFrame {
         cursor.put_u8(command);
         cursor.put_u16_le(data_length as u16);
         cursor.put_u32_le(stream_id);
-        writer.write(&buf).await?;
+        writer.write_all(&buf).await?;
         if let MuxFrame::Push(f) = self {
-            writer.write(&f.data).await?;
+            writer.write_all(&f.data).await?;
         }
         writer.flush().await?;
         Ok(())
diff --git a/src/protocol/socks5/mod.rs b/src/protocol/socks5/mod.rs
index fe6fd1c..2a6b587 100644
--- a/src/protocol/socks5/mod.rs
+++ b/src/protocol/socks5/mod.rs
@@ -114,7 +114,7 @@ impl TcpResponseHeader {
     {
         let mut buf = BytesMut::with_capacity(self.serialized_len());
         self.write_to_buf(&mut buf);
-        w.write(&buf).await?;
+        w.write_all(&buf).await?;
         Ok(())
     }

diff --git a/src/protocol/trojan/mod.rs b/src/protocol/trojan/mod.rs
index 1ab97b4..72e7e7a 100644
--- a/src/protocol/trojan/mod.rs
+++ b/src/protocol/trojan/mod.rs
@@ -122,7 +122,7 @@ impl RequestHeader {
         addr.write_to_buf(cursor);
         cursor.put_slice(crlf);

-        w.write(&buf).await?;
+        w.write_all(&buf).await?;
         Ok(())
     }
 }
@@ -173,7 +173,7 @@ impl UdpHeader {
         self.address.write_to_buf(cursor);
         cursor.put_u16(self.payload_len);
         cursor.put_slice(b"\r\n");
-        w.write(&buf).await?;
+        w.write_all(&buf).await?;
         Ok(())
     }
 }
diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs
index d0e3342..0f7736a 100644
--- a/src/proxy/mod.rs
+++ b/src/proxy/mod.rs
@@ -36,7 +36,7 @@ use crate::{
     },
 };

-const RELAY_BUFFER_SIZE: usize = 0x4000;
+const RELAY_BUFFER_SIZE: usize = 0x2000;

 async fn copy_udp<R: UdpRead, W: UdpWrite>(r: &mut R, w: &mut W) -> io::Result<()> {
     let mut buf = [0u8; RELAY_BUFFER_SIZE];
@@ -61,7 +61,7 @@ async fn copy_tcp<R: AsyncRead + Unpin, W: AsyncWrite + Unpin>(
         if len == 0 {
             break;
         }
-        w.write(&buf[..len]).await?;
+        w.write_all(&buf[..len]).await?;
         w.flush().await?;
     }
     Ok(())
naresh97 commented 5 months ago

For my implementation, I think I will work on latency, performance and memory once I finish all the features that I think are important.

What are some things about the caddy-trojan server that you find very convenient? I would like to make this software convenient to use as well.

cattyhouse commented 5 months ago

caddy-trojan is a plugin for caddy, so it does these all in one:

  1. Automatic free certs and 80 redirection
  2. A simple file server that handles non-trojan protocol fallback
  3. A normal behavior to handle plain request on port 443 like http://domain:443
  4. Very simple config
  5. Great performance due to go concurrency

Down side is that ( usually not a problem on server side):

  1. Huge binary size 40M+
  2. Higher memory usage 40M at least
naresh97 commented 5 months ago

These are some good features, I would like to implement the following

24 - Implement Port 80 redirection

25 - Implement simple file server

naresh97 commented 5 months ago

@cattyhouse regarding the UDP socks proxy, I'm curious about your use case. There doesn't seem to be many UDP SOCKS5 proxy clients out there, how do you usually use UDP over SOCKS5?

cattyhouse commented 5 months ago

it is used in some cases like:

  1. DNS proxy, like proxying 1.1.1.1 udp, i don't use this any more, because all my DNS are sent via DoT which is TLS TCP on port 853
  2. QUIC traffic, e.g. Google Chrome with QUIC enabled when watching Youtube video, however chrome will fall back to TCP when it found that QUIC is not reachable.

actually, there are other cases, but i don't use them.

right now i am using a tool called ipt2socks to turn socks5 to tproxy which does transparent proxying: {tcp udp} -> ipt2socks -> socks5 -> trojan client -> trojan server -> out, if udp is missing, this chain is not complete...

naresh97 commented 5 months ago

Yes, 1. and 2. are certainly interesting use cases.

For your use case with ipt2socks: your use case may also be satisfied if the client natively supported incoming transparent proxy connections (tcp & udp), correct?

cattyhouse commented 5 months ago

Yes, that's right, if trojan client supports tproxy( trojan-go for instance ) then ipt2socks is not needed