diff --git a/Cargo.lock b/Cargo.lock index 4f716f0eac0d..33ed99130006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,12 +18,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "ctr", "opaque-debug 0.3.0", ] +[[package]] +name = "aes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +dependencies = [ + "cfg-if", + "cipher 0.4.3", + "cpufeatures", +] + [[package]] name = "aes-gcm" version = "0.9.4" @@ -31,8 +42,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.7.5", + "cipher 0.3.0", "ctr", "ghash", "subtle", @@ -45,8 +56,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.7.5", + "cipher 0.3.0", "ctr", "polyval", "subtle", @@ -80,6 +91,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-trait" version = "0.1.53" @@ -132,6 +155,20 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake3" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.3", +] + [[package]] name = "block-buffer" version = "0.7.3" @@ -235,7 +272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a9cf981c7e62b6fb02225592ee7ebf221e0b0b5317984a57a1e9d21af20e317" dependencies = [ "aead", - "cipher", + "cipher 0.3.0", "ctr", "subtle", ] @@ -253,7 +290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] @@ -266,7 +303,7 @@ checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -299,6 +336,16 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "3.1.8" @@ -330,6 +377,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation" version = "0.9.3" @@ -403,7 +456,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -949,6 +1002,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.5", +] + [[package]] name = "instant" version = "0.1.12" @@ -2004,6 +2066,7 @@ dependencies = [ name = "shadowsocks" version = "1.14.3" dependencies = [ + "aes 0.8.1", "arc-swap 1.5.0", "async-trait", "base64", @@ -2037,13 +2100,14 @@ dependencies = [ [[package]] name = "shadowsocks-crypto" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd381517e3eb8fec5090696debfdea972d8afe6fc926c26c7bfd5fee9053efbd" +version = "0.4.0" +source = "git+https://github.com/shadowsocks/shadowsocks-crypto.git#0b5d9ec81d4a7c59fa6379057b81087ffd0f7001" dependencies = [ - "aes", + "aes 0.7.5", "aes-gcm", "aes-gcm-siv", + "blake3", + "bytes", "ccm", "cfg-if", "chacha20", diff --git a/Cargo.toml b/Cargo.toml index a17a299d6005..78fb61b4977a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,9 @@ stream-cipher = ["shadowsocks-service/stream-cipher"] # WARN: These non-standard AEAD ciphers are not officially supported by shadowsocks community aead-cipher-extra = ["shadowsocks-service/aead-cipher-extra"] +# Enable AEAD 2022 +aead-cipher-2022 = ["shadowsocks-service/aead-cipher-2022"] + # Enable detection against replay attack security-replay-attack-detect = ["shadowsocks-service/security-replay-attack-detect"] replay-attack-detect = ["security-replay-attack-detect"] # Backward compatibility. DO NOT USE. diff --git a/bin/ssurl.rs b/bin/ssurl.rs index 7284f10a78e9..178e81210732 100644 --- a/bin/ssurl.rs +++ b/bin/ssurl.rs @@ -3,7 +3,7 @@ //! SS-URI = "ss://" userinfo "@" hostname ":" port [ "/" ] [ "?" plugin ] [ "#" tag ] //! userinfo = websafe-base64-encode-utf8(method ":" password) -use clap::{Command, Arg}; +use clap::{Arg, Command}; use qrcode::{types::Color, QrCode}; use shadowsocks_service::{ diff --git a/crates/shadowsocks-service/Cargo.toml b/crates/shadowsocks-service/Cargo.toml index ec0774719050..b3a516e82e92 100644 --- a/crates/shadowsocks-service/Cargo.toml +++ b/crates/shadowsocks-service/Cargo.toml @@ -38,7 +38,7 @@ dns-over-tls = ["trust-dns", "trust-dns-resolver/dns-over-tls", "trust-dns-resol dns-over-https = ["trust-dns", "trust-dns-resolver/dns-over-https", "trust-dns-resolver/dns-over-https-rustls"] # Enable DNS-relay -local-dns = ["local", "trust-dns", "rand"] +local-dns = ["local", "trust-dns"] # Backward compatibility, DO NOT USE local-dns-relay = ["local-dns"] # Enable client flow statistic report @@ -56,7 +56,7 @@ local-tunnel = ["local"] # Enable socks4 protocol for sslocal local-socks4 = ["local"] # Enable Tun interface protocol for sslocal -local-tun = ["local", "etherparse", "tun", "rand", "smoltcp"] +local-tun = ["local", "etherparse", "tun", "smoltcp"] # Enable Stream Cipher Protocol # WARN: Stream Cipher Protocol is proved to be insecure @@ -68,6 +68,9 @@ stream-cipher = ["shadowsocks/stream-cipher"] # WARN: These non-standard AEAD ciphers are not officially supported by shadowsocks community aead-cipher-extra = ["shadowsocks/aead-cipher-extra"] +# Enable AEAD 2022 +aead-cipher-2022 = ["shadowsocks/aead-cipher-2022"] + # Enable detection against replay attack security-replay-attack-detect = ["shadowsocks/security-replay-attack-detect"] # Enable IV printable prefix @@ -92,7 +95,7 @@ lru_time_cache = "0.11" bytes = "1.0" byte_string = "1.0" byteorder = "1.3" -rand = { version = "0.8", optional = true } +rand = "0.8" futures = "0.3" tokio = { version = "1.5", features = ["io-util", "macros", "net", "parking_lot", "rt", "sync", "time"] } diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index 86fa37d66d1a..d5f735526373 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -66,7 +66,7 @@ use serde::{Deserialize, Serialize}; use shadowsocks::relay::socks5::Address; use shadowsocks::{ config::{ManagerAddr, Mode, ReplayAttackPolicy, ServerAddr, ServerConfig, ServerWeight}, - crypto::v1::CipherKind, + crypto::CipherKind, plugin::PluginConfig, }; #[cfg(feature = "trust-dns")] diff --git a/crates/shadowsocks-service/src/local/http/connector.rs b/crates/shadowsocks-service/src/local/http/connector.rs index c9afec080d90..414595e1383e 100644 --- a/crates/shadowsocks-service/src/local/http/connector.rs +++ b/crates/shadowsocks-service/src/local/http/connector.rs @@ -31,9 +31,9 @@ impl Connector { } impl Service for Connector { - type Response = ProxyHttpStream; type Error = io::Error; type Future = Connecting; + type Response = ProxyHttpStream; fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -67,7 +67,7 @@ impl Service for Connector { } } } - .boxed(), + .boxed(), } } } diff --git a/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs b/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs index 330d55a4e6c5..1c04e9ae5131 100644 --- a/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs +++ b/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs @@ -11,12 +11,9 @@ use std::{ use pin_project::pin_project; use shadowsocks::{ net::TcpStream, - relay::{ - socks5::Address, - tcprelay::proxy_stream::{ProxyClientStream, ProxyClientStreamReadHalf, ProxyClientStreamWriteHalf}, - }, + relay::{socks5::Address, tcprelay::proxy_stream::ProxyClientStream}, }; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf, ReadHalf, WriteHalf}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::{ local::{context::ServiceContext, loadbalancing::ServerIdent}, @@ -160,93 +157,3 @@ impl From>> for AutoProxyClientStrea AutoProxyClientStream::Proxied(s) } } - -impl AutoProxyClientStream { - pub fn into_split(self) -> (AutoProxyClientStreamReadHalf, AutoProxyClientStreamWriteHalf) { - match self { - AutoProxyClientStream::Proxied(s) => { - let (r, w) = s.into_split(); - ( - AutoProxyClientStreamReadHalf::Proxied(r), - AutoProxyClientStreamWriteHalf::Proxied(w), - ) - } - AutoProxyClientStream::Bypassed(s) => { - let (r, w) = tokio::io::split(s); - ( - AutoProxyClientStreamReadHalf::Bypassed(r), - AutoProxyClientStreamWriteHalf::Bypassed(w), - ) - } - } - } -} - -#[allow(clippy::large_enum_variant)] -#[pin_project(project = AutoProxyClientStreamReadHalfProj)] -pub enum AutoProxyClientStreamReadHalf { - Proxied(#[pin] ProxyClientStreamReadHalf>), - Bypassed(#[pin] ReadHalf), -} - -impl AutoProxyIo for AutoProxyClientStreamReadHalf { - fn is_proxied(&self) -> bool { - matches!(*self, AutoProxyClientStreamReadHalf::Proxied(..)) - } -} - -impl AsyncRead for AutoProxyClientStreamReadHalf { - fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - match self.project() { - AutoProxyClientStreamReadHalfProj::Proxied(s) => s.poll_read(cx, buf), - AutoProxyClientStreamReadHalfProj::Bypassed(s) => s.poll_read(cx, buf), - } - } -} - -#[allow(clippy::large_enum_variant)] -#[pin_project(project = AutoProxyClientStreamWriteHalfProj)] -pub enum AutoProxyClientStreamWriteHalf { - Proxied(#[pin] ProxyClientStreamWriteHalf>), - Bypassed(#[pin] WriteHalf), -} - -impl AutoProxyIo for AutoProxyClientStreamWriteHalf { - fn is_proxied(&self) -> bool { - matches!(*self, AutoProxyClientStreamWriteHalf::Proxied(..)) - } -} - -impl AsyncWrite for AutoProxyClientStreamWriteHalf { - fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - match self.project() { - AutoProxyClientStreamWriteHalfProj::Proxied(s) => s.poll_write(cx, buf), - AutoProxyClientStreamWriteHalfProj::Bypassed(s) => s.poll_write(cx, buf), - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - match self.project() { - AutoProxyClientStreamWriteHalfProj::Proxied(s) => s.poll_flush(cx), - AutoProxyClientStreamWriteHalfProj::Bypassed(s) => s.poll_flush(cx), - } - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - match self.project() { - AutoProxyClientStreamWriteHalfProj::Proxied(s) => s.poll_shutdown(cx), - AutoProxyClientStreamWriteHalfProj::Bypassed(s) => s.poll_shutdown(cx), - } - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - bufs: &[IoSlice<'_>], - ) -> Poll> { - match self.project() { - AutoProxyClientStreamWriteHalfProj::Proxied(s) => s.poll_write_vectored(cx, bufs), - AutoProxyClientStreamWriteHalfProj::Bypassed(s) => s.poll_write_vectored(cx, bufs), - } - } -} diff --git a/crates/shadowsocks-service/src/local/net/udp/association.rs b/crates/shadowsocks-service/src/local/net/udp/association.rs index b18fb950ccf0..b04e9aa42e9b 100644 --- a/crates/shadowsocks-service/src/local/net/udp/association.rs +++ b/crates/shadowsocks-service/src/local/net/udp/association.rs @@ -1,6 +1,7 @@ //! UDP Association Managing use std::{ + cell::RefCell, io::{self, ErrorKind}, marker::PhantomData, net::SocketAddr, @@ -13,13 +14,14 @@ use bytes::Bytes; use futures::future; use log::{debug, error, trace, warn}; use lru_time_cache::LruCache; +use rand::{rngs::SmallRng, Rng, SeedableRng}; use tokio::{sync::mpsc, task::JoinHandle, time}; use shadowsocks::{ lookup_then, net::UdpSocket as ShadowUdpSocket, relay::{ - udprelay::{ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, + udprelay::{options::UdpSocketControlData, ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, Address, }, }; @@ -170,6 +172,24 @@ where } } +#[derive(Debug, Clone, Copy)] +struct ServerContext { + last_packet_id: u64, +} + +#[derive(Clone)] +struct ServerSessionContext { + server_session_map: LruCache, +} + +impl ServerSessionContext { + fn new() -> ServerSessionContext { + ServerSessionContext { + server_session_map: LruCache::with_expiry_duration_and_capacity(Duration::from_secs(30 * 60), 5), + } + } +} + struct UdpAssociationContext where W: UdpInboundWrite + Send + Sync + Unpin + 'static, @@ -183,6 +203,9 @@ where keepalive_flag: bool, balancer: PingBalancer, respond_writer: W, + client_session_id: u64, + client_packet_id: u64, + server_session: Option, } impl Drop for UdpAssociationContext @@ -194,6 +217,15 @@ where } } +thread_local! { + static CLIENT_SESSION_RNG: RefCell = RefCell::new(SmallRng::from_entropy()); +} + +#[inline] +fn generate_client_session_id() -> u64 { + CLIENT_SESSION_RNG.with(|rng| rng.borrow_mut().gen()) +} + impl UdpAssociationContext where W: UdpInboundWrite + Send + Sync + Unpin + 'static, @@ -220,6 +252,11 @@ where keepalive_flag: false, balancer, respond_writer, + // client_session_id must be random generated, + // server use this ID to identify every independent clients. + client_session_id: generate_client_session_id(), + client_packet_id: 1, + server_session: None, }; let handle = tokio::spawn(async move { assoc.dispatch_packet(receiver).await }); @@ -277,7 +314,7 @@ where } received_opt = receive_from_proxied_opt(&self.proxied_socket, &mut proxied_buffer) => { - let (n, addr) = match received_opt { + let (n, addr, control_opt) = match received_opt { Ok(r) => r, Err(err) => { error!("udp relay {} <- ... (proxied) failed, error: {}", self.peer_addr, err); @@ -287,6 +324,36 @@ where } }; + if let Some(control) = control_opt { + // Check if Packet ID is in the window + const SERVER_UDP_PACKET_WINDOW_SIZE: u64 = 64; + + let session = self.server_session.get_or_insert_with(ServerSessionContext::new); + + let packet_id = control.packet_id; + let session_context = session + .server_session_map + .entry(control.server_session_id) + .or_insert_with(|| ServerContext { + last_packet_id: packet_id, + }); + + let smallest_packet_id = if session_context.last_packet_id <= SERVER_UDP_PACKET_WINDOW_SIZE { + 0 + } else { + session_context.last_packet_id - SERVER_UDP_PACKET_WINDOW_SIZE + }; + + if packet_id < smallest_packet_id { + error!("udp {} packet_id {} out of window", self.peer_addr, packet_id); + return; + } + + if packet_id > session_context.last_packet_id { + session_context.last_packet_id = packet_id; + } + } + self.send_received_respond_packet(&addr, &proxied_buffer[..n], false).await; } @@ -322,14 +389,14 @@ where async fn receive_from_proxied_opt( socket: &Option, buf: &mut Vec, - ) -> io::Result<(usize, Address)> { + ) -> io::Result<(usize, Address, Option)> { match *socket { None => future::pending().await, Some(ref s) => { if buf.is_empty() { buf.resize(MAXIMUM_UDP_PAYLOAD_SIZE, 0); } - s.recv(buf).await + s.recv_with_ctrl(buf).await } } } @@ -434,7 +501,27 @@ where } }; - match socket.send(target_addr, data).await { + // Increase Packet ID before send + self.client_packet_id = match self.client_packet_id.checked_add(1) { + Some(i) => i, + None => { + warn!( + "{} -> {} (proxied) sending {} bytes failed, packet id overflowed", + self.peer_addr, + target_addr, + data.len(), + ); + return Ok(()); + } + }; + + let control = UdpSocketControlData { + client_session_id: self.client_session_id, + server_session_id: 0, + packet_id: self.client_packet_id, + }; + + match socket.send_with_ctrl(target_addr, &control, data).await { Ok(..) => return Ok(()), Err(err) => { debug!( diff --git a/crates/shadowsocks-service/src/local/tunnel/udprelay.rs b/crates/shadowsocks-service/src/local/tunnel/udprelay.rs index 514053a78d8d..cb31cfaac821 100644 --- a/crates/shadowsocks-service/src/local/tunnel/udprelay.rs +++ b/crates/shadowsocks-service/src/local/tunnel/udprelay.rs @@ -1,6 +1,7 @@ //! UDP Tunnel server use std::{ + cell::RefCell, io::{self, ErrorKind}, net::SocketAddr, sync::Arc, @@ -11,12 +12,13 @@ use bytes::Bytes; use futures::future; use log::{debug, error, info, trace, warn}; use lru_time_cache::LruCache; +use rand::{rngs::SmallRng, Rng, SeedableRng}; use shadowsocks::{ lookup_then, net::UdpSocket as ShadowUdpSocket, relay::{ socks5::Address, - udprelay::{ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, + udprelay::{options::UdpSocketControlData, ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, }, ServerAddr, }; @@ -197,6 +199,24 @@ impl UdpAssociation { } } +#[derive(Debug, Clone, Copy)] +struct ServerContext { + last_packet_id: u64, +} + +#[derive(Clone)] +struct ServerSessionContext { + server_session_map: LruCache, +} + +impl ServerSessionContext { + fn new() -> ServerSessionContext { + ServerSessionContext { + server_session_map: LruCache::with_expiry_duration_and_capacity(Duration::from_secs(30 * 60), 5), + } + } +} + struct UdpAssociationContext { context: Arc, peer_addr: SocketAddr, @@ -206,6 +226,9 @@ struct UdpAssociationContext { keepalive_flag: bool, balancer: PingBalancer, inbound: Arc, + client_session_id: u64, + client_packet_id: u64, + server_session: Option, } impl Drop for UdpAssociationContext { @@ -214,6 +237,15 @@ impl Drop for UdpAssociationContext { } } +thread_local! { + static CLIENT_SESSION_RNG: RefCell = RefCell::new(SmallRng::from_entropy()); +} + +#[inline] +fn generate_client_session_id() -> u64 { + CLIENT_SESSION_RNG.with(|rng| rng.borrow_mut().gen()) +} + impl UdpAssociationContext { fn create( context: Arc, @@ -237,6 +269,11 @@ impl UdpAssociationContext { keepalive_flag: false, balancer, inbound, + // client_session_id must be random generated, + // server use this ID to identify every independent clients. + client_session_id: generate_client_session_id(), + client_packet_id: 1, + server_session: None, }; let handle = tokio::spawn(async move { assoc.dispatch_packet(receiver).await }); @@ -262,7 +299,7 @@ impl UdpAssociationContext { } received_opt = receive_from_proxied_opt(&self.proxied_socket, &mut proxied_buffer) => { - let (n, addr) = match received_opt { + let (n, addr, control_opt) = match received_opt { Ok(r) => r, Err(err) => { error!("udp relay {} <- ... failed, error: {}", self.peer_addr, err); @@ -272,6 +309,36 @@ impl UdpAssociationContext { } }; + if let Some(control) = control_opt { + // Check if Packet ID is in the window + const SERVER_UDP_PACKET_WINDOW_SIZE: u64 = 64; + + let session = self.server_session.get_or_insert_with(ServerSessionContext::new); + + let packet_id = control.packet_id; + let session_context = session + .server_session_map + .entry(control.server_session_id) + .or_insert_with(|| ServerContext { + last_packet_id: packet_id, + }); + + let smallest_packet_id = if session_context.last_packet_id <= SERVER_UDP_PACKET_WINDOW_SIZE { + 0 + } else { + session_context.last_packet_id - SERVER_UDP_PACKET_WINDOW_SIZE + }; + + if packet_id < smallest_packet_id { + error!("udp {} packet_id {} out of window", self.peer_addr, packet_id); + return; + } + + if packet_id > session_context.last_packet_id { + session_context.last_packet_id = packet_id; + } + } + self.send_received_respond_packet(&addr, &proxied_buffer[..n]).await; } @@ -291,14 +358,14 @@ impl UdpAssociationContext { async fn receive_from_proxied_opt( socket: &Option, buf: &mut Vec, - ) -> io::Result<(usize, Address)> { + ) -> io::Result<(usize, Address, Option)> { match *socket { None => future::pending().await, Some(ref s) => { if buf.is_empty() { buf.resize(MAXIMUM_UDP_PAYLOAD_SIZE, 0); } - s.recv(buf).await + s.recv_with_ctrl(buf).await } } } @@ -341,7 +408,27 @@ impl UdpAssociationContext { } }; - match socket.send(&self.forward_addr, data).await { + // Increase Packet ID before send + self.client_packet_id = match self.client_packet_id.checked_add(1) { + Some(i) => i, + None => { + warn!( + "{} -> {} (proxied) sending {} bytes failed, packet id overflowed", + self.peer_addr, + self.forward_addr, + data.len(), + ); + return Ok(()); + } + }; + + let control = UdpSocketControlData { + client_session_id: self.client_session_id, + server_session_id: 0, + packet_id: self.client_packet_id, + }; + + match socket.send_with_ctrl(&self.forward_addr, &control, data).await { Ok(..) => return Ok(()), Err(err) => { debug!( diff --git a/crates/shadowsocks-service/src/manager/server.rs b/crates/shadowsocks-service/src/manager/server.rs index 1ccac048372d..3455cc76700a 100644 --- a/crates/shadowsocks-service/src/manager/server.rs +++ b/crates/shadowsocks-service/src/manager/server.rs @@ -8,7 +8,7 @@ use log::{error, info, trace}; use shadowsocks::{ config::{Mode, ServerConfig, ServerType}, context::{Context, SharedContext}, - crypto::v1::CipherKind, + crypto::CipherKind, dns_resolver::DnsResolver, manager::protocol::{ self, diff --git a/crates/shadowsocks-service/src/net/mon_socket.rs b/crates/shadowsocks-service/src/net/mon_socket.rs index 0b4a1ab08c91..7bf541d2fe13 100644 --- a/crates/shadowsocks-service/src/net/mon_socket.rs +++ b/crates/shadowsocks-service/src/net/mon_socket.rs @@ -2,7 +2,10 @@ use std::{io, net::SocketAddr, sync::Arc}; -use shadowsocks::{relay::socks5::Address, ProxySocket}; +use shadowsocks::{ + relay::{socks5::Address, udprelay::options::UdpSocketControlData}, + ProxySocket, +}; use tokio::net::ToSocketAddrs; use super::flow::FlowStat; @@ -28,6 +31,20 @@ impl MonProxySocket { Ok(()) } + /// Send a UDP packet to addr through proxy + #[inline] + pub async fn send_with_ctrl( + &self, + addr: &Address, + control: &UdpSocketControlData, + payload: &[u8], + ) -> io::Result<()> { + let n = self.socket.send_with_ctrl(addr, control, payload).await?; + self.flow_stat.incr_tx(n as u64); + + Ok(()) + } + /// Send a UDP packet to target from proxy #[inline] pub async fn send_to(&self, target: A, addr: &Address, payload: &[u8]) -> io::Result<()> { @@ -37,6 +54,21 @@ impl MonProxySocket { Ok(()) } + /// Send a UDP packet to target from proxy + #[inline] + pub async fn send_to_with_ctrl( + &self, + target: A, + addr: &Address, + control: &UdpSocketControlData, + payload: &[u8], + ) -> io::Result<()> { + let n = self.socket.send_to_with_ctrl(target, addr, control, payload).await?; + self.flow_stat.incr_tx(n as u64); + + Ok(()) + } + /// Receive packet from Shadowsocks' UDP server /// /// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet @@ -50,6 +82,22 @@ impl MonProxySocket { Ok((n, addr)) } + /// Receive packet from Shadowsocks' UDP server + /// + /// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet + /// + /// It is recommended to allocate a buffer to have at least 65536 bytes. + #[inline] + pub async fn recv_with_ctrl( + &self, + recv_buf: &mut [u8], + ) -> io::Result<(usize, Address, Option)> { + let (n, addr, recv_n, control) = self.socket.recv_with_ctrl(recv_buf).await?; + self.flow_stat.incr_rx(recv_n as u64); + + Ok((n, addr, control)) + } + /// Receive packet from Shadowsocks' UDP server /// /// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet @@ -63,8 +111,29 @@ impl MonProxySocket { Ok((n, peer_addr, addr)) } + /// Receive packet from Shadowsocks' UDP server + /// + /// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet + /// + /// It is recommended to allocate a buffer to have at least 65536 bytes. + #[inline] + pub async fn recv_from_with_ctrl( + &self, + recv_buf: &mut [u8], + ) -> io::Result<(usize, SocketAddr, Address, Option)> { + let (n, peer_addr, addr, recv_n, control) = self.socket.recv_from_with_ctrl(recv_buf).await?; + self.flow_stat.incr_rx(recv_n as u64); + + Ok((n, peer_addr, addr, control)) + } + #[inline] pub fn get_ref(&self) -> &ProxySocket { &self.socket } + + #[inline] + pub fn flow_stat(&self) -> &FlowStat { + &self.flow_stat + } } diff --git a/crates/shadowsocks-service/src/server/server.rs b/crates/shadowsocks-service/src/server/server.rs index 0fa4f06fa20d..86030afe8bfb 100644 --- a/crates/shadowsocks-service/src/server/server.rs +++ b/crates/shadowsocks-service/src/server/server.rs @@ -171,6 +171,7 @@ impl Server { async fn run_udp_server(&self) -> io::Result<()> { let server = UdpServer::new( self.context.clone(), + self.svr_cfg.method(), self.udp_expiry_duration, self.udp_capacity, self.accept_opts.clone(), diff --git a/crates/shadowsocks-service/src/server/tcprelay.rs b/crates/shadowsocks-service/src/server/tcprelay.rs index 3c8c0a20e716..54cf6c990a19 100644 --- a/crates/shadowsocks-service/src/server/tcprelay.rs +++ b/crates/shadowsocks-service/src/server/tcprelay.rs @@ -10,12 +10,9 @@ use std::{ use log::{debug, error, info, trace, warn}; use shadowsocks::{ - crypto::v1::CipherKind, + crypto::CipherKind, net::{AcceptOpts, TcpStream as OutboundTcpStream}, - relay::{ - socks5::{Address, Error as Socks5Error}, - tcprelay::{utils::copy_encrypted_bidirectional, ProxyServerStream}, - }, + relay::tcprelay::{utils::copy_encrypted_bidirectional, ProxyServerStream}, ProxyListener, ServerConfig, }; @@ -107,9 +104,17 @@ struct TcpServerClient { impl TcpServerClient { async fn serve(mut self) -> io::Result<()> { - let target_addr = match Address::read_from(&mut self.stream).await { + // let target_addr = match Address::read_from(&mut self.stream).await { + let target_addr = match self.stream.handshake().await { Ok(a) => a, - Err(Socks5Error::IoError(ref err)) if err.kind() == ErrorKind::UnexpectedEof => { + // Err(Socks5Error::IoError(ref err)) if err.kind() == ErrorKind::UnexpectedEof => { + // debug!( + // "handshake failed, received EOF before a complete target Address, peer: {}", + // self.peer_addr + // ); + // return Ok(()); + // } + Err(err) if err.kind() == ErrorKind::UnexpectedEof => { debug!( "handshake failed, received EOF before a complete target Address, peer: {}", self.peer_addr diff --git a/crates/shadowsocks-service/src/server/udprelay.rs b/crates/shadowsocks-service/src/server/udprelay.rs index db0286a27029..9bff40e47b84 100644 --- a/crates/shadowsocks-service/src/server/udprelay.rs +++ b/crates/shadowsocks-service/src/server/udprelay.rs @@ -3,7 +3,10 @@ use std::{ io::{self, ErrorKind}, net::SocketAddr, - sync::Arc, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, time::Duration, }; @@ -12,11 +15,12 @@ use futures::future; use log::{debug, error, info, trace, warn}; use lru_time_cache::LruCache; use shadowsocks::{ + crypto::{CipherCategory, CipherKind}, lookup_then, net::{AcceptOpts, UdpSocket as OutboundUdpSocket}, relay::{ socks5::Address, - udprelay::{ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, + udprelay::{options::UdpSocketControlData, ProxySocket, MAXIMUM_UDP_PAYLOAD_SIZE}, }, ServerConfig, }; @@ -26,13 +30,50 @@ use crate::net::{MonProxySocket, UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE, UDP_AS use super::context::ServiceContext; +#[derive(Debug, Clone, Copy)] +enum NatKey { + PeerAddr(SocketAddr), + SessionId(u64), +} + type AssociationMap = LruCache; +type SessionMap = LruCache; + +enum NatMap { + Association(AssociationMap), + Session(SessionMap), +} + +impl NatMap { + fn cleanup_expired(&mut self) { + match *self { + NatMap::Association(ref mut m) => { + m.iter(); + } + NatMap::Session(ref mut m) => { + m.iter(); + } + } + } + + fn keep_alive(&mut self, key: &NatKey) { + match (self, key) { + (NatMap::Association(ref mut m), NatKey::PeerAddr(ref peer_addr)) => { + m.get(peer_addr); + } + (NatMap::Session(ref mut m), NatKey::SessionId(ref session_id)) => { + m.get(session_id); + } + _ => unreachable!("NatMap & NatKey mismatch"), + } + } +} pub struct UdpServer { context: Arc, - assoc_map: AssociationMap, - keepalive_tx: mpsc::Sender, - keepalive_rx: mpsc::Receiver, + assoc_map: NatMap, + keepalive_tx: mpsc::Sender, + keepalive_rx: mpsc::Receiver, time_to_live: Duration, accept_opts: AcceptOpts, } @@ -40,14 +81,31 @@ pub struct UdpServer { impl UdpServer { pub fn new( context: Arc, + method: CipherKind, time_to_live: Option, capacity: Option, accept_opts: AcceptOpts, ) -> UdpServer { let time_to_live = time_to_live.unwrap_or(crate::DEFAULT_UDP_EXPIRY_DURATION); - let assoc_map = match capacity { - Some(capacity) => LruCache::with_expiry_duration_and_capacity(time_to_live, capacity), - None => LruCache::with_expiry_duration(time_to_live), + + fn create_assoc_map(time_to_live: Duration, capacity: Option) -> LruCache + where + K: Ord + Clone, + { + match capacity { + Some(capacity) => LruCache::with_expiry_duration_and_capacity(time_to_live, capacity), + None => LruCache::with_expiry_duration(time_to_live), + } + } + + let assoc_map = match method.category() { + CipherCategory::None | CipherCategory::Aead => { + NatMap::Association(create_assoc_map(time_to_live, capacity)) + } + #[cfg(feature = "stream-cipher")] + CipherCategory::Stream => NatMap::Association(create_assoc_map(time_to_live, capacity)), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => NatMap::Session(create_assoc_map(time_to_live, capacity)), }; let (keepalive_tx, keepalive_rx) = mpsc::channel(UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE); @@ -80,16 +138,16 @@ impl UdpServer { tokio::select! { _ = cleanup_timer.tick() => { // cleanup expired associations. iter() will remove expired elements - let _ = self.assoc_map.iter(); + let _ = self.assoc_map.cleanup_expired(); } peer_addr_opt = self.keepalive_rx.recv() => { let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly"); - self.assoc_map.get(&peer_addr); + self.assoc_map.keep_alive(&peer_addr); } - recv_result = listener.recv_from(&mut buffer) => { - let (n, peer_addr, target_addr) = match recv_result { + recv_result = listener.recv_from_with_ctrl(&mut buffer) => { + let (n, peer_addr, target_addr, control) = match recv_result { Ok(s) => s, Err(err) => { error!("udp server recv_from failed with error: {}", err); @@ -122,7 +180,7 @@ impl UdpServer { } let data = &buffer[..n]; - if let Err(err) = self.send_packet(&listener, peer_addr, target_addr, data).await { + if let Err(err) = self.send_packet(&listener, peer_addr, target_addr, control, data).await { error!( "udp packet relay {} with {} bytes failed, error: {}", peer_addr, @@ -140,23 +198,59 @@ impl UdpServer { listener: &Arc, peer_addr: SocketAddr, target_addr: Address, + control: Option, data: &[u8], ) -> io::Result<()> { - if let Some(assoc) = self.assoc_map.get(&peer_addr) { - return assoc.try_send((target_addr, Bytes::copy_from_slice(data))); - } + match self.assoc_map { + NatMap::Association(ref mut m) => { + if let Some(assoc) = m.get(&peer_addr) { + return assoc.try_send((peer_addr, target_addr, Bytes::copy_from_slice(data), control)); + } - let assoc = UdpAssociation::new( - self.context.clone(), - listener.clone(), - peer_addr, - self.keepalive_tx.clone(), - ); + let assoc = UdpAssociation::new_association( + self.context.clone(), + listener.clone(), + peer_addr, + self.keepalive_tx.clone(), + ); - debug!("created udp association for {}", peer_addr); + debug!("created udp association for {}", peer_addr); - assoc.try_send((target_addr, Bytes::copy_from_slice(data)))?; - self.assoc_map.insert(peer_addr, assoc); + assoc.try_send((peer_addr, target_addr, Bytes::copy_from_slice(data), control))?; + m.insert(peer_addr, assoc); + } + NatMap::Session(ref mut m) => { + let xcontrol = match control { + None => { + error!("control is required for session based NAT, from {}", peer_addr); + return Err(io::Error::new(ErrorKind::Other, "control data missing in packet")); + } + Some(ref c) => c, + }; + + let client_session_id = xcontrol.client_session_id; + + if let Some(assoc) = m.get(&client_session_id) { + return assoc.try_send((peer_addr, target_addr, Bytes::copy_from_slice(data), control)); + } + + let assoc = UdpAssociation::new_session( + self.context.clone(), + listener.clone(), + peer_addr, + self.keepalive_tx.clone(), + client_session_id, + ); + + debug!( + "created udp association for {} with session {}", + peer_addr, client_session_id + ); + + assoc.try_send((peer_addr, target_addr, Bytes::copy_from_slice(data), control))?; + m.insert(client_session_id, assoc); + } + } Ok(()) } @@ -164,7 +258,7 @@ impl UdpServer { struct UdpAssociation { assoc_handle: JoinHandle<()>, - sender: mpsc::Sender<(Address, Bytes)>, + sender: mpsc::Sender<(SocketAddr, Address, Bytes, Option)>, } impl Drop for UdpAssociation { @@ -174,17 +268,29 @@ impl Drop for UdpAssociation { } impl UdpAssociation { - fn new( + fn new_association( + context: Arc, + inbound: Arc, + peer_addr: SocketAddr, + keepalive_tx: mpsc::Sender, + ) -> UdpAssociation { + let (assoc_handle, sender) = UdpAssociationContext::create(context, inbound, peer_addr, keepalive_tx, None); + UdpAssociation { assoc_handle, sender } + } + + fn new_session( context: Arc, inbound: Arc, peer_addr: SocketAddr, - keepalive_tx: mpsc::Sender, + keepalive_tx: mpsc::Sender, + client_session_id: u64, ) -> UdpAssociation { - let (assoc_handle, sender) = UdpAssociationContext::create(context, inbound, peer_addr, keepalive_tx); + let (assoc_handle, sender) = + UdpAssociationContext::create(context, inbound, peer_addr, keepalive_tx, Some(client_session_id)); UdpAssociation { assoc_handle, sender } } - fn try_send(&self, data: (Address, Bytes)) -> io::Result<()> { + fn try_send(&self, data: (SocketAddr, Address, Bytes, Option)) -> io::Result<()> { if let Err(..) = self.sender.try_send(data) { let err = io::Error::new(ErrorKind::Other, "udp relay channel full"); return Err(err); @@ -193,14 +299,36 @@ impl UdpAssociation { } } +struct ClientContext { + last_packet_id: u64, +} + +struct ClientSessionContext { + client_session_id: u64, + client_context_map: LruCache, +} + +impl ClientSessionContext { + fn new(client_session_id: u64) -> ClientSessionContext { + ClientSessionContext { + client_session_id, + client_context_map: LruCache::with_expiry_duration_and_capacity(Duration::from_secs(30 * 60), 10), + } + } +} + struct UdpAssociationContext { context: Arc, peer_addr: SocketAddr, outbound_ipv4_socket: Option, outbound_ipv6_socket: Option, - keepalive_tx: mpsc::Sender, + keepalive_tx: mpsc::Sender, keepalive_flag: bool, inbound: Arc, + // AEAD 2022 + client_session: Option, + server_session_id: u64, + server_packet_id: u64, } impl Drop for UdpAssociationContext { @@ -214,13 +342,20 @@ impl UdpAssociationContext { context: Arc, inbound: Arc, peer_addr: SocketAddr, - keepalive_tx: mpsc::Sender, - ) -> (JoinHandle<()>, mpsc::Sender<(Address, Bytes)>) { + keepalive_tx: mpsc::Sender, + client_session_id: Option, + ) -> ( + JoinHandle<()>, + mpsc::Sender<(SocketAddr, Address, Bytes, Option)>, + ) { // Pending packets UDP_ASSOCIATION_SEND_CHANNEL_SIZE for each association should be good enough for a server. // If there are plenty of packets stuck in the channel, dropping excessive packets is a good way to protect the server from // being OOM. let (sender, receiver) = mpsc::channel(UDP_ASSOCIATION_SEND_CHANNEL_SIZE); + // Server Session ID allocats sequentially preventing duplication + static SERVER_SESSION_ID_ALLOCATOR: AtomicU64 = AtomicU64::new(1); + let mut assoc = UdpAssociationContext { context, peer_addr, @@ -229,13 +364,19 @@ impl UdpAssociationContext { keepalive_tx, keepalive_flag: false, inbound, + client_session: client_session_id.map(ClientSessionContext::new), + server_session_id: SERVER_SESSION_ID_ALLOCATOR.fetch_add(1, Ordering::AcqRel), + server_packet_id: 0, }; let handle = tokio::spawn(async move { assoc.dispatch_packet(receiver).await }); (handle, sender) } - async fn dispatch_packet(&mut self, mut receiver: mpsc::Receiver<(Address, Bytes)>) { + async fn dispatch_packet( + &mut self, + mut receiver: mpsc::Receiver<(SocketAddr, Address, Bytes, Option)>, + ) { let mut outbound_ipv4_buffer = Vec::new(); let mut outbound_ipv6_buffer = Vec::new(); let mut keepalive_interval = time::interval(Duration::from_secs(1)); @@ -243,7 +384,7 @@ impl UdpAssociationContext { loop { tokio::select! { packet_received_opt = receiver.recv() => { - let (target_addr, data) = match packet_received_opt { + let (peer_addr, target_addr, data, control) = match packet_received_opt { Some(d) => d, None => { trace!("udp association for {} -> ... channel closed", self.peer_addr); @@ -251,7 +392,7 @@ impl UdpAssociationContext { } }; - self.dispatch_received_packet(&target_addr, &data).await; + self.dispatch_received_packet(peer_addr, &target_addr, &data, &control).await; } received_opt = receive_from_outbound_opt(&self.outbound_ipv4_socket, &mut outbound_ipv4_buffer) => { @@ -286,8 +427,13 @@ impl UdpAssociationContext { _ = keepalive_interval.tick() => { if self.keepalive_flag { - if let Err(..) = self.keepalive_tx.try_send(self.peer_addr) { - debug!("udp relay {} keep-alive failed, channel full or closed", self.peer_addr); + let nat_key = match self.client_session { + None => NatKey::PeerAddr(self.peer_addr), + Some(ref s) => NatKey::SessionId(s.client_session_id), + }; + + if let Err(..) = self.keepalive_tx.try_send(nat_key) { + debug!("udp relay {:?} keep-alive failed, channel full or closed", nat_key); } else { self.keepalive_flag = false; } @@ -313,12 +459,29 @@ impl UdpAssociationContext { } } - async fn dispatch_received_packet(&mut self, target_addr: &Address, data: &[u8]) { + async fn dispatch_received_packet( + &mut self, + peer_addr: SocketAddr, + target_addr: &Address, + data: &[u8], + control: &Option, + ) { + if let Some(ref mut session) = self.client_session { + if peer_addr != self.peer_addr { + debug!( + "udp relay for {} changed to {}, session: {:?}", + self.peer_addr, peer_addr, session.client_session_id + ); + self.peer_addr = peer_addr; + } + } + trace!( - "udp relay {} -> {} with {} bytes", + "udp relay {} -> {} with {} bytes, control: {:?}", self.peer_addr, target_addr, - data.len() + data.len(), + control, ); if self.context.check_outbound_blocked(target_addr).await { @@ -329,6 +492,38 @@ impl UdpAssociationContext { return; } + if let Some(control) = control { + // Check if Packet ID is in the window + const SERVER_UDP_PACKET_WINDOW_SIZE: u64 = 256; + + let session = self + .client_session + .get_or_insert_with(|| ClientSessionContext::new(control.client_session_id)); + + let session_context = session + .client_context_map + .entry(self.peer_addr) + .or_insert_with(|| ClientContext { + last_packet_id: control.packet_id, + }); + + let packet_id = control.packet_id; + let smallest_packet_id = if session_context.last_packet_id <= SERVER_UDP_PACKET_WINDOW_SIZE { + 0 + } else { + session_context.last_packet_id - SERVER_UDP_PACKET_WINDOW_SIZE + }; + + if packet_id < smallest_packet_id { + error!("udp client {} packet_id {} out of window", self.peer_addr, packet_id); + return; + } + + if packet_id > session_context.last_packet_id { + session_context.last_packet_id = packet_id; + } + } + if let Err(err) = self.dispatch_received_outbound_packet(target_addr, data).await { error!( "udp relay {} -> {} with {} bytes, error: {}", @@ -392,17 +587,67 @@ impl UdpAssociationContext { // Keep association alive in map self.keepalive_flag = true; - // Send back to client - if let Err(err) = self.inbound.send_to(self.peer_addr, addr, data).await { - warn!( - "udp failed to send back {} bytes to client {}, from target {}, error: {}", - data.len(), - self.peer_addr, - addr, - err - ); - } else { - trace!("udp relay {} <- {} with {} bytes", self.peer_addr, addr, data.len()); + match self.client_session { + None => { + // Naive route, send data directly back to client without session + if let Err(err) = self.inbound.send_to(self.peer_addr, addr, data).await { + warn!( + "udp failed to send back {} bytes to client {}, from target {}, error: {}", + data.len(), + self.peer_addr, + addr, + err + ); + } else { + trace!("udp relay {} <- {} with {} bytes", self.peer_addr, addr, data.len()); + } + } + Some(ref client_session) => { + // AEAD 2022, client session + + // Increase Packet ID before send + self.server_packet_id = match self.server_packet_id.checked_add(1) { + Some(i) => i, + None => { + warn!( + "udp failed to send back {} bytes to client {}, from target {}, server packet id overflowed", + data.len(), + self.peer_addr, + addr + ); + return; + } + }; + + let control = UdpSocketControlData { + client_session_id: client_session.client_session_id, + server_session_id: self.server_session_id, + packet_id: self.server_packet_id, + }; + + if let Err(err) = self + .inbound + .send_to_with_ctrl(self.peer_addr, addr, &control, data) + .await + { + warn!( + "udp failed to send back {} bytes to client {}, from target {}, control: {:?}, error: {}", + data.len(), + self.peer_addr, + addr, + control, + err + ); + } else { + trace!( + "udp relay {} <- {} with {} bytes, control {:?}", + self.peer_addr, + addr, + data.len(), + control + ); + } + } } } } diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index 1c106a567806..56c114b5d3cc 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -31,6 +31,9 @@ stream-cipher = ["shadowsocks-crypto/v1-stream"] # WARN: These non-standard AEAD ciphers are not officially supported by shadowsocks community aead-cipher-extra = ["shadowsocks-crypto/v1-aead-extra"] +# Enable AEAD 2022 +aead-cipher-2022 = ["shadowsocks-crypto/v2", "rand/small_rng", "aes"] + # Enable detection against replay attack security-replay-attack-detect = ["bloomfilter", "spin"] # Enable IV printable prefix @@ -72,11 +75,13 @@ trust-dns-resolver = { version = "0.21", optional = true } arc-swap = { version = "1.3", optional = true } notify = { version = "5.0.0-pre.13", optional = true } +aes = { version = "0.8", optional = true } + [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] -shadowsocks-crypto = { version = "0.3.3", features = ["ring"] } +shadowsocks-crypto = { version = "0.4", git = "https://github.com/shadowsocks/shadowsocks-crypto.git", features = ["ring"] } [target.'cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies] -shadowsocks-crypto = { version = "0.3.3", features = [] } +shadowsocks-crypto = { version = "0.4", git = "https://github.com/shadowsocks/shadowsocks-crypto.git", features = [] } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["mswsock", "winsock2", "netioapi"] } diff --git a/crates/shadowsocks/src/config.rs b/crates/shadowsocks/src/config.rs index 4be50f62e177..7b534ba37fbe 100644 --- a/crates/shadowsocks/src/config.rs +++ b/crates/shadowsocks/src/config.rs @@ -15,7 +15,7 @@ use log::error; use url::{self, Url}; use crate::{ - crypto::v1::{openssl_bytes_to_key, CipherKind}, + crypto::{v1::openssl_bytes_to_key, CipherKind}, plugin::PluginConfig, relay::socks5::Address, }; diff --git a/crates/shadowsocks/src/relay/tcprelay/aead.rs b/crates/shadowsocks/src/relay/tcprelay/aead.rs index c3e579165495..6dc3461dfa95 100644 --- a/crates/shadowsocks/src/relay/tcprelay/aead.rs +++ b/crates/shadowsocks/src/relay/tcprelay/aead.rs @@ -48,7 +48,7 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::{ context::Context, - crypto::v1::{Cipher, CipherKind}, + crypto::{v1::Cipher, CipherKind}, }; /// AEAD packet payload must be smaller than 0x3FFF @@ -93,6 +93,10 @@ impl DecryptedReader { } } + pub fn salt(&self) -> Option<&[u8]> { + self.salt.as_deref() + } + /// Attempt to read decrypted data from stream pub fn poll_read_decrypted( &mut self, @@ -219,9 +223,8 @@ impl DecryptedReader { } // Check repeated salt after first successful decryption #442 - if self.salt.is_some() { - let salt = self.salt.take().unwrap(); - context.check_nonce_replay(&salt)?; + if let Some(ref salt) = self.salt { + context.check_nonce_replay(salt)?; } // Remote TAG @@ -298,6 +301,7 @@ pub struct EncryptedWriter { cipher: Cipher, buffer: BytesMut, state: EncryptWriteState, + salt: Bytes, } impl EncryptedWriter { @@ -311,9 +315,15 @@ impl EncryptedWriter { cipher: Cipher::new(method, key, nonce), buffer, state: EncryptWriteState::AssemblePacket, + salt: Bytes::copy_from_slice(nonce), } } + /// Salt (nonce) + pub fn salt(&self) -> &[u8] { + self.salt.as_ref() + } + /// Attempt to write encrypted data into the writer pub fn poll_write_encrypted( &mut self, diff --git a/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs b/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs new file mode 100644 index 000000000000..ce78dce06f1a --- /dev/null +++ b/crates/shadowsocks/src/relay/tcprelay/aead_2022.rs @@ -0,0 +1,371 @@ +//! AEAD 2022 packet I/O facilities +//! +//! ```plain +//! TCP request (before encryption) +//! +------+---------------------+------------------+ +//! | ATYP | Destination Address | Destination Port | +//! +------+---------------------+------------------+ +//! | 1 | Variable | 2 | +//! +------+---------------------+------------------+ +//! +//! TCP request (after encryption, *ciphertext*) +//! +--------+--------------+------------------+--------------+---------------+ +//! | NONCE | *HeaderLen* | HeaderLen_TAG | *Header* | Header_TAG | +//! +--------+--------------+------------------+--------------+---------------+ +//! | Fixed | 2 | Fixed | Variable | Fixed | +//! +--------+--------------+------------------+--------------+---------------+ +//! +//! TCP Chunk (before encryption) +//! +----------+ +//! | DATA | +//! +----------+ +//! | Variable | +//! +----------+ +//! +//! TCP Chunk (after encryption, *ciphertext*) +//! +--------------+---------------+--------------+------------+ +//! | *DataLen* | DataLen_TAG | *Data* | Data_TAG | +//! +--------------+---------------+--------------+------------+ +//! | 2 | Fixed | Variable | Fixed | +//! +--------------+---------------+--------------+------------+ +//! ``` +use std::{ + io::{self, ErrorKind}, + marker::Unpin, + pin::Pin, + slice, + task::{self, Poll}, + u16, +}; + +use byte_string::ByteStr; +use bytes::{BufMut, Bytes, BytesMut}; +use futures::ready; +use log::trace; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +use crate::{ + context::Context, + crypto::{v2::tcp::TcpCipher, CipherKind}, +}; + +/// AEAD packet payload must be smaller than 0xFFFF (u16::MAX) +pub const MAX_PACKET_SIZE: usize = 0xFFFF; + +enum DecryptReadState { + WaitSalt { key: Bytes }, + ReadLength, + ReadData { length: usize }, + BufferedData { pos: usize }, +} + +/// Reader wrapper that will decrypt data automatically +pub struct DecryptedReader { + state: DecryptReadState, + cipher: Option, + buffer: BytesMut, + method: CipherKind, + salt: Option, +} + +impl DecryptedReader { + pub fn new(method: CipherKind, key: &[u8]) -> DecryptedReader { + if method.salt_len() > 0 { + DecryptedReader { + state: DecryptReadState::WaitSalt { + key: Bytes::copy_from_slice(key), + }, + cipher: None, + buffer: BytesMut::with_capacity(method.salt_len()), + method, + salt: None, + } + } else { + DecryptedReader { + state: DecryptReadState::ReadLength, + cipher: Some(TcpCipher::new(method, key, &[])), + buffer: BytesMut::with_capacity(2 + method.tag_len()), + method, + salt: None, + } + } + } + + pub fn salt(&self) -> Option<&[u8]> { + self.salt.as_deref() + } + + /// Attempt to read decrypted data from stream + pub fn poll_read_decrypted( + &mut self, + cx: &mut task::Context<'_>, + context: &Context, + stream: &mut S, + buf: &mut ReadBuf<'_>, + ) -> Poll> + where + S: AsyncRead + Unpin + ?Sized, + { + loop { + match self.state { + DecryptReadState::WaitSalt { ref key } => { + let key = unsafe { &*(key.as_ref() as *const _) }; + ready!(self.poll_read_salt(cx, stream, key))?; + + self.buffer.clear(); + self.state = DecryptReadState::ReadLength; + self.buffer.reserve(2 + self.method.tag_len()); + } + DecryptReadState::ReadLength => match ready!(self.poll_read_length(cx, stream))? { + None => { + return Ok(()).into(); + } + Some(length) => { + self.buffer.clear(); + self.state = DecryptReadState::ReadData { length }; + self.buffer.reserve(length + self.method.tag_len()); + } + }, + DecryptReadState::ReadData { length } => { + ready!(self.poll_read_data(cx, context, stream, length))?; + + self.state = DecryptReadState::BufferedData { pos: 0 }; + } + DecryptReadState::BufferedData { ref mut pos } => { + if *pos < self.buffer.len() { + let buffered = &self.buffer[*pos..]; + + let consumed = usize::min(buffered.len(), buf.remaining()); + buf.put_slice(&buffered[..consumed]); + + *pos += consumed; + + return Ok(()).into(); + } + + self.buffer.clear(); + self.state = DecryptReadState::ReadLength; + self.buffer.reserve(2 + self.method.tag_len()); + } + } + } + } + + fn poll_read_salt(&mut self, cx: &mut task::Context<'_>, stream: &mut S, key: &[u8]) -> Poll> + where + S: AsyncRead + Unpin + ?Sized, + { + let salt_len = self.method.salt_len(); + + let n = ready!(self.poll_read_exact(cx, stream, salt_len))?; + if n < salt_len { + return Err(ErrorKind::UnexpectedEof.into()).into(); + } + + let salt = &self.buffer[..salt_len]; + // #442 Remember salt in filter after first successful decryption. + // + // If we check salt right here will allow attacker to flood our filter and eventually block all of our legitimate clients' requests. + self.salt = Some(Bytes::copy_from_slice(salt)); + + trace!("got AEAD salt {:?}", ByteStr::new(salt)); + + let cipher = TcpCipher::new(self.method, key, salt); + + self.cipher = Some(cipher); + + Ok(()).into() + } + + fn poll_read_length(&mut self, cx: &mut task::Context<'_>, stream: &mut S) -> Poll>> + where + S: AsyncRead + Unpin + ?Sized, + { + let length_len = 2 + self.method.tag_len(); + + let n = ready!(self.poll_read_exact(cx, stream, length_len))?; + if n == 0 { + return Ok(None).into(); + } + + let cipher = self.cipher.as_mut().expect("cipher is None"); + + let m = &mut self.buffer[..length_len]; + let length = DecryptedReader::decrypt_length(cipher, m)?; + + Ok(Some(length)).into() + } + + fn poll_read_data( + &mut self, + cx: &mut task::Context<'_>, + context: &Context, + stream: &mut S, + size: usize, + ) -> Poll> + where + S: AsyncRead + Unpin + ?Sized, + { + let data_len = size + self.method.tag_len(); + + let n = ready!(self.poll_read_exact(cx, stream, data_len))?; + if n == 0 { + return Err(ErrorKind::UnexpectedEof.into()).into(); + } + + let cipher = self.cipher.as_mut().expect("cipher is None"); + + let m = &mut self.buffer[..data_len]; + if !cipher.decrypt_packet(m) { + return Err(io::Error::new(ErrorKind::Other, "invalid tag-in")).into(); + } + + // Check repeated salt after first successful decryption #442 + if let Some(ref salt) = self.salt { + context.check_nonce_replay(salt)?; + } + + // Remote TAG + self.buffer.truncate(size); + + Ok(()).into() + } + + fn poll_read_exact(&mut self, cx: &mut task::Context<'_>, stream: &mut S, size: usize) -> Poll> + where + S: AsyncRead + Unpin + ?Sized, + { + assert!(size != 0); + + while self.buffer.len() < size { + let remaining = size - self.buffer.len(); + let buffer = &mut self.buffer.chunk_mut()[..remaining]; + + let mut read_buf = + ReadBuf::uninit(unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut _, remaining) }); + ready!(Pin::new(&mut *stream).poll_read(cx, &mut read_buf))?; + + let n = read_buf.filled().len(); + if n == 0 { + if !self.buffer.is_empty() { + return Err(ErrorKind::UnexpectedEof.into()).into(); + } else { + return Ok(0).into(); + } + } + + unsafe { + self.buffer.advance_mut(n); + } + } + + Ok(size).into() + } + + fn decrypt_length(cipher: &mut TcpCipher, m: &mut [u8]) -> io::Result { + let plen = { + if !cipher.decrypt_packet(m) { + return Err(io::Error::new(ErrorKind::Other, "invalid tag-in")); + } + + u16::from_be_bytes([m[0], m[1]]) as usize + }; + + Ok(plen) + } +} + +enum EncryptWriteState { + AssemblePacket, + Writing { pos: usize }, +} + +/// Writer wrapper that will encrypt data automatically +pub struct EncryptedWriter { + cipher: TcpCipher, + buffer: BytesMut, + state: EncryptWriteState, + salt: Bytes, +} + +impl EncryptedWriter { + /// Creates a new EncryptedWriter + pub fn new(method: CipherKind, key: &[u8], nonce: &[u8]) -> EncryptedWriter { + // nonce should be sent with the first packet + let mut buffer = BytesMut::with_capacity(nonce.len()); + buffer.put(nonce); + + EncryptedWriter { + cipher: TcpCipher::new(method, key, nonce), + buffer, + state: EncryptWriteState::AssemblePacket, + salt: Bytes::copy_from_slice(nonce), + } + } + + /// Salt (nonce) + pub fn salt(&self) -> &[u8] { + self.salt.as_ref() + } + + /// Attempt to write encrypted data into the writer + pub fn poll_write_encrypted( + &mut self, + cx: &mut task::Context<'_>, + stream: &mut S, + mut buf: &[u8], + ) -> Poll> + where + S: AsyncWrite + Unpin + ?Sized, + { + if buf.len() > MAX_PACKET_SIZE { + buf = &buf[..MAX_PACKET_SIZE]; + } + + loop { + match self.state { + EncryptWriteState::AssemblePacket => { + // Step 1. Append Length + let length_size = 2 + self.cipher.tag_len(); + self.buffer.reserve(length_size); + + let mbuf = &mut self.buffer.chunk_mut()[..length_size]; + let mbuf = unsafe { slice::from_raw_parts_mut(mbuf.as_mut_ptr(), mbuf.len()) }; + + self.buffer.put_u16(buf.len() as u16); + self.cipher.encrypt_packet(mbuf); + unsafe { self.buffer.advance_mut(self.cipher.tag_len()) }; + + // Step 2. Append data + let data_size = buf.len() + self.cipher.tag_len(); + self.buffer.reserve(data_size); + + let mbuf = &mut self.buffer.chunk_mut()[..data_size]; + let mbuf = unsafe { slice::from_raw_parts_mut(mbuf.as_mut_ptr(), mbuf.len()) }; + + self.buffer.put_slice(buf); + self.cipher.encrypt_packet(mbuf); + unsafe { self.buffer.advance_mut(self.cipher.tag_len()) }; + + // Step 3. Write all + self.state = EncryptWriteState::Writing { pos: 0 }; + } + EncryptWriteState::Writing { ref mut pos } => { + while *pos < self.buffer.len() { + let n = ready!(Pin::new(&mut *stream).poll_write(cx, &self.buffer[*pos..]))?; + if n == 0 { + return Err(ErrorKind::UnexpectedEof.into()).into(); + } + *pos += n; + } + + // Reset state + self.state = EncryptWriteState::AssemblePacket; + self.buffer.clear(); + + return Ok(buf.len()).into(); + } + } + } + } +} diff --git a/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs b/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs index 14ee6d17eb9f..e31a8e7c585f 100644 --- a/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs +++ b/crates/shadowsocks/src/relay/tcprelay/crypto_io.rs @@ -13,10 +13,12 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf, ReadHalf, WriteHalf}; use crate::{ context::Context, - crypto::v1::{CipherCategory, CipherKind}, + crypto::{CipherCategory, CipherKind}, }; use super::aead::{DecryptedReader as AeadDecryptedReader, EncryptedWriter as AeadEncryptedWriter}; +#[cfg(feature = "aead-cipher-2022")] +use super::aead_2022::{DecryptedReader as Aead2022DecryptedReader, EncryptedWriter as Aead2022EncryptedWriter}; #[cfg(feature = "stream-cipher")] use super::stream::{DecryptedReader as StreamDecryptedReader, EncryptedWriter as StreamEncryptedWriter}; @@ -27,6 +29,8 @@ pub enum DecryptedReader { Aead(AeadDecryptedReader), #[cfg(feature = "stream-cipher")] Stream(StreamDecryptedReader), + #[cfg(feature = "aead-cipher-2022")] + Aead2022(Aead2022DecryptedReader), } impl DecryptedReader { @@ -37,6 +41,8 @@ impl DecryptedReader { CipherCategory::Stream => DecryptedReader::Stream(StreamDecryptedReader::new(method, key)), CipherCategory::Aead => DecryptedReader::Aead(AeadDecryptedReader::new(method, key)), CipherCategory::None => DecryptedReader::None, + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => DecryptedReader::Aead2022(Aead2022DecryptedReader::new(method, key)), } } @@ -57,6 +63,20 @@ impl DecryptedReader { DecryptedReader::Stream(ref mut reader) => reader.poll_read_decrypted(cx, context, stream, buf), DecryptedReader::Aead(ref mut reader) => reader.poll_read_decrypted(cx, context, stream, buf), DecryptedReader::None => Pin::new(stream).poll_read(cx, buf), + #[cfg(feature = "aead-cipher-2022")] + DecryptedReader::Aead2022(ref mut reader) => reader.poll_read_decrypted(cx, context, stream, buf), + } + } + + /// Get received IV (Stream) or Salt (AEAD, AEAD2022) + pub fn nonce(&self) -> Option<&[u8]> { + match *self { + #[cfg(feature = "stream-cipher")] + DecryptedReader::Stream(ref reader) => reader.iv(), + DecryptedReader::Aead(ref reader) => reader.salt(), + DecryptedReader::None => None, + #[cfg(feature = "aead-cipher-2022")] + DecryptedReader::Aead2022(ref reader) => reader.salt(), } } } @@ -67,6 +87,8 @@ pub enum EncryptedWriter { Aead(AeadEncryptedWriter), #[cfg(feature = "stream-cipher")] Stream(StreamEncryptedWriter), + #[cfg(feature = "aead-cipher-2022")] + Aead2022(Aead2022EncryptedWriter), } impl EncryptedWriter { @@ -77,6 +99,8 @@ impl EncryptedWriter { CipherCategory::Stream => EncryptedWriter::Stream(StreamEncryptedWriter::new(method, key, nonce)), CipherCategory::Aead => EncryptedWriter::Aead(AeadEncryptedWriter::new(method, key, nonce)), CipherCategory::None => EncryptedWriter::None, + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => EncryptedWriter::Aead2022(Aead2022EncryptedWriter::new(method, key, nonce)), } } @@ -96,6 +120,20 @@ impl EncryptedWriter { EncryptedWriter::Stream(ref mut writer) => writer.poll_write_encrypted(cx, stream, buf), EncryptedWriter::Aead(ref mut writer) => writer.poll_write_encrypted(cx, stream, buf), EncryptedWriter::None => Pin::new(stream).poll_write(cx, buf), + #[cfg(feature = "aead-cipher-2022")] + EncryptedWriter::Aead2022(ref mut writer) => writer.poll_write_encrypted(cx, stream, buf), + } + } + + /// Get sent IV (Stream) or Salt (AEAD, AEAD2022) + pub fn nonce(&self) -> &[u8] { + match *self { + #[cfg(feature = "stream-cipher")] + EncryptedWriter::Stream(ref writer) => writer.iv(), + EncryptedWriter::Aead(ref writer) => writer.salt(), + EncryptedWriter::None => &[], + #[cfg(feature = "aead-cipher-2022")] + EncryptedWriter::Aead2022(ref writer) => writer.salt(), } } } @@ -123,6 +161,8 @@ impl CryptoStream { CipherCategory::Stream => method.iv_len(), CipherCategory::Aead => method.salt_len(), CipherCategory::None => 0, + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => method.salt_len(), }; let iv = match category { @@ -140,6 +180,13 @@ impl CryptoStream { local_salt } CipherCategory::None => Vec::new(), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => { + let mut local_salt = vec![0u8; prev_len]; + context.generate_nonce(&mut local_salt, true); + trace!("generated AEAD cipher salt {:?}", ByteStr::new(&local_salt)); + local_salt + } }; CryptoStream { @@ -173,6 +220,31 @@ impl CryptoStream { pub fn into_inner(self) -> S { self.stream } + + /// Get received IV (Stream) or Salt (AEAD, AEAD2022) + pub fn received_nonce(&self) -> Option<&[u8]> { + self.dec.nonce() + } + + /// Get sent IV (Stream) or Salt (AEAD, AEAD2022) + pub fn sent_nonce(&self) -> &[u8] { + self.enc.nonce() + } +} + +/// Cryptographic reader trait +pub trait CryptoRead { + fn poll_read_decrypted( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + context: &Context, + buf: &mut ReadBuf<'_>, + ) -> Poll>; +} + +/// Cryptographic writer trait +pub trait CryptoWrite { + fn poll_write_encrypted(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll>; } impl CryptoStream { @@ -182,32 +254,51 @@ impl CryptoStream { } } -impl CryptoStream +impl CryptoRead for CryptoStream where S: AsyncRead + AsyncWrite + Unpin, { /// Attempt to read decrypted data from `stream` #[inline] - pub fn poll_read_decrypted( - &mut self, + fn poll_read_decrypted( + mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, context: &Context, buf: &mut ReadBuf<'_>, ) -> Poll> { - self.dec.poll_read_decrypted(cx, context, &mut self.stream, buf) + let CryptoStream { + ref mut dec, + ref mut stream, + .. + } = *self; + dec.poll_read_decrypted(cx, context, stream, buf) } } -impl CryptoStream +impl CryptoWrite for CryptoStream where S: AsyncRead + AsyncWrite + Unpin, { /// Attempt to write encrypted data to `stream` #[inline] - pub fn poll_write_encrypted(&mut self, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - self.enc.poll_write_encrypted(cx, &mut self.stream, buf) + fn poll_write_encrypted( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + buf: &[u8], + ) -> Poll> { + let CryptoStream { + ref mut enc, + ref mut stream, + .. + } = *self; + enc.poll_write_encrypted(cx, stream, buf) } +} +impl CryptoStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ /// Polls `flush` on the underlying stream #[inline] pub fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll> { @@ -257,18 +348,33 @@ impl CryptoStreamReadHalf { } impl CryptoStreamReadHalf +where + S: AsyncRead + Unpin, +{ + /// Get received IV (Stream) or Salt (AEAD, AEAD2022) + pub fn nonce(&self) -> Option<&[u8]> { + self.dec.nonce() + } +} + +impl CryptoRead for CryptoStreamReadHalf where S: AsyncRead + Unpin, { /// Attempt to read decrypted data from `stream` #[inline] - pub fn poll_read_decrypted( - &mut self, + fn poll_read_decrypted( + mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, context: &Context, buf: &mut ReadBuf<'_>, ) -> Poll> { - self.dec.poll_read_decrypted(cx, context, &mut self.reader, buf) + let CryptoStreamReadHalf { + ref mut dec, + ref mut reader, + .. + } = *self; + dec.poll_read_decrypted(cx, context, reader, buf) } } @@ -283,18 +389,37 @@ impl CryptoStreamWriteHalf { pub fn method(&self) -> CipherKind { self.method } + + /// Get sent IV (Stream) or Salt (AEAD, AEAD2022) + pub fn sent_nonce(&self) -> &[u8] { + self.enc.nonce() + } } -impl CryptoStreamWriteHalf +impl CryptoWrite for CryptoStreamWriteHalf where S: AsyncWrite + Unpin, { /// Attempt to write encrypted data to `stream` #[inline] - pub fn poll_write_encrypted(&mut self, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - self.enc.poll_write_encrypted(cx, &mut self.writer, buf) + fn poll_write_encrypted( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + buf: &[u8], + ) -> Poll> { + let CryptoStreamWriteHalf { + ref mut enc, + ref mut writer, + .. + } = *self; + enc.poll_write_encrypted(cx, writer, buf) } +} +impl CryptoStreamWriteHalf +where + S: AsyncWrite + Unpin, +{ /// Polls `flush` on the underlying stream #[inline] pub fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll> { diff --git a/crates/shadowsocks/src/relay/tcprelay/mod.rs b/crates/shadowsocks/src/relay/tcprelay/mod.rs index 465b1aa7e5fb..8aeda85b1399 100644 --- a/crates/shadowsocks/src/relay/tcprelay/mod.rs +++ b/crates/shadowsocks/src/relay/tcprelay/mod.rs @@ -6,9 +6,20 @@ pub use self::{ }; mod aead; +#[cfg(feature = "aead-cipher-2022")] +mod aead_2022; pub mod crypto_io; pub mod proxy_listener; pub mod proxy_stream; #[cfg(feature = "stream-cipher")] mod stream; pub mod utils; + +/// Connection direction type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StreamType { + /// Connection initiated from client to server + Client, + /// Connection initiated from server to client + Server, +} diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs index c3f1e23bca76..4bbd2bd99c50 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs @@ -11,7 +11,7 @@ use tokio::{ use crate::{ config::{ServerAddr, ServerConfig}, context::SharedContext, - crypto::v1::CipherKind, + crypto::CipherKind, net::{AcceptOpts, TcpListener}, relay::tcprelay::proxy_stream::server::ProxyServerStream, }; diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs index 955c1bdd32e8..e9610a5e5ca2 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs @@ -18,11 +18,12 @@ use tokio::{ use crate::{ config::ServerConfig, - context::SharedContext, + context::{Context, SharedContext}, + crypto::CipherKind, net::{ConnectOpts, TcpStream as OutboundTcpStream}, relay::{ socks5::Address, - tcprelay::crypto_io::{CryptoStream, CryptoStreamReadHalf, CryptoStreamWriteHalf}, + tcprelay::crypto_io::{CryptoRead, CryptoStream, CryptoWrite}, }, }; @@ -32,12 +33,19 @@ enum ProxyClientStreamWriteState { Connected, } +enum ProxyClientStreamReadState { + #[cfg(feature = "aead-cipher-2022")] + WaitHeader(BytesMut, usize), + Established, +} + /// A stream for sending / receiving data stream from remote server via shadowsocks' proxy server #[pin_project] pub struct ProxyClientStream { #[pin] stream: CryptoStream, - state: ProxyClientStreamWriteState, + writer_state: ProxyClientStreamWriteState, + reader_state: ProxyClientStreamReadState, context: SharedContext, } @@ -141,9 +149,21 @@ where let addr = addr.into(); let stream = CryptoStream::from_stream(&context, stream, svr_cfg.method(), svr_cfg.key()); + #[cfg(not(feature = "aead-cipher-2022"))] + let reader_state = ProxyClientStreamReadState::Established; + + #[cfg(feature = "aead-cipher-2022")] + let reader_state = if svr_cfg.method().is_aead_2022() { + // AEAD 2022 has a respond header + ProxyClientStreamReadState::WaitHeader(BytesMut::new(), 0) + } else { + ProxyClientStreamReadState::Established + }; + ProxyClientStream { stream, - state: ProxyClientStreamWriteState::Connect(addr), + writer_state: ProxyClientStreamWriteState::Connect(addr), + reader_state, context, } } @@ -164,164 +184,211 @@ where } } -impl AsyncRead for ProxyClientStream +#[cfg(feature = "aead-cipher-2022")] +fn poll_read_aead_2022_header( + context: &Context, + mut stream: Pin<&mut CryptoStream>, + cx: &mut task::Context<'_>, + header_buf: &mut BytesMut, + header_pos: &mut usize, +) -> Poll> where S: AsyncRead + AsyncWrite + Unpin, { - #[inline] - fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - let mut this = self.project(); - this.stream.poll_read_decrypted(cx, this.context, buf) + use bytes::Buf; + use std::time::SystemTime; + + // AEAD 2022 TCP Response Header + // + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + // | TYPE | UNIX TIMESTAMP | + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + // | Request SALT (Variable ...) + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + + const SERVER_STREAM_TYPE: u8 = 1; + const SERVER_STREAM_TIMESTAMP_MAX_DIFF: u64 = 30; + + // Initialize buffer + let method = stream.method(); + if header_buf.is_empty() { + header_buf.resize(1 + 8 + method.salt_len(), 0); + *header_pos = 0; } -} - -impl AsyncWrite for ProxyClientStream -where - S: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - let mut this = self.project(); - - loop { - match this.state { - ProxyClientStreamWriteState::Connect(ref addr) => { - // Target Address should be sent with the first packet together, - // which would prevent from being detected by connection features. - - let addr_length = addr.serialized_len(); - - let mut buffer = BytesMut::with_capacity(addr_length + buf.len()); - addr.write_to_buf(&mut buffer); - buffer.put_slice(buf); - - // Save the concatenated buffer before it is written successfully. - // APIs require buffer to be kept alive before Poll::Ready - // - // Proactor APIs like IOCP on Windows, pointers of buffers have to be kept alive - // before IO completion. - *(this.state) = ProxyClientStreamWriteState::Connecting(buffer); - } - ProxyClientStreamWriteState::Connecting(ref buffer) => { - let n = ready!(this.stream.poll_write_encrypted(cx, buffer))?; - // In general, poll_write_encrypted should perform like write_all. - debug_assert!(n == buffer.len()); + while *header_pos < header_buf.len() { + let remaining_buf = &mut header_buf[*header_pos..]; + let mut read_buf = ReadBuf::new(remaining_buf); - *(this.state) = ProxyClientStreamWriteState::Connected; + ready!(stream.as_mut().poll_read_decrypted(cx, context, &mut read_buf))?; - // NOTE: - // poll_write will return Ok(0) if buf.len() == 0 - // But for the first call, this function will eventually send the handshake packet (IV/Salt + ADDR) to the remote address. - // - // https://github.com/shadowsocks/shadowsocks-rust/issues/232 - // - // For protocols that requires *Server Hello* message, like FTP, clients won't send anything to the server until server sends handshake messages. - // This could be achieved by calling poll_write with an empty input buffer. - return Ok(buf.len()).into(); - } - ProxyClientStreamWriteState::Connected => { - return this.stream.poll_write_encrypted(cx, buf); - } - } - } + *header_pos += read_buf.filled().len(); } - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().stream.poll_flush(cx) + // Done reading TCP header, check all the fields + + let stream_type = header_buf.get_u8(); + if stream_type != SERVER_STREAM_TYPE { + return Err(io::Error::new( + ErrorKind::Other, + format!("received TCP response header with wrong type {}", stream_type), + )) + .into(); } - #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().stream.poll_shutdown(cx) + let timestamp = header_buf.get_u64(); + let now = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_secs(), + Err(_) => panic!("SystemTime::now() is before UNIX Epoch!"), + }; + + if timestamp > now || now - timestamp > SERVER_STREAM_TIMESTAMP_MAX_DIFF { + return Err(io::Error::new( + ErrorKind::Other, + format!("received TCP response header with aged timestamp: {}", timestamp), + )) + .into(); } -} -impl ProxyClientStream -where - S: AsyncRead + AsyncWrite + Unpin, -{ - /// Splits into reader and writer halves - pub fn into_split(self) -> (ProxyClientStreamReadHalf, ProxyClientStreamWriteHalf) { - // Cannot split if stream is still pending - assert!( - !matches!(self.state, ProxyClientStreamWriteState::Connecting(..)), - "stream is pending on writing the first packet" - ); - let (reader, writer) = self.stream.into_split(); - ( - ProxyClientStreamReadHalf { - reader, - context: self.context, - }, - ProxyClientStreamWriteHalf { - writer, - state: self.state, - }, - ) + let salt = &header_buf[..]; + if salt != stream.sent_nonce() { + return Err(io::Error::new( + ErrorKind::Other, + "received TCP response header with unmatched salt", + )) + .into(); } -} -/// Owned read half produced by `ProxyClientStream::into_split` -#[pin_project] -pub struct ProxyClientStreamReadHalf { - #[pin] - reader: CryptoStreamReadHalf, - context: SharedContext, + Ok(()).into() } -impl AsyncRead for ProxyClientStreamReadHalf +impl AsyncRead for ProxyClientStream where - S: AsyncRead + Unpin, + S: AsyncRead + AsyncWrite + Unpin, { #[inline] fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { let mut this = self.project(); - this.reader.poll_read_decrypted(cx, this.context, buf) + + loop { + match this.reader_state { + ProxyClientStreamReadState::Established => { + return this.stream.poll_read_decrypted(cx, this.context, buf); + } + #[cfg(feature = "aead-cipher-2022")] + ProxyClientStreamReadState::WaitHeader(ref mut buf, ref mut buf_pos) => { + ready!(poll_read_aead_2022_header( + this.context, + this.stream.as_mut(), + cx, + buf, + buf_pos, + ))?; + *(this.reader_state) = ProxyClientStreamReadState::Established; + } + } + } } } -/// Owned write half produced by `ProxyClientStream::into_split` -#[pin_project] -pub struct ProxyClientStreamWriteHalf { - #[pin] - writer: CryptoStreamWriteHalf, - state: ProxyClientStreamWriteState, +#[inline] +fn make_first_packet_buffer(method: CipherKind, addr: &Address, buf: &[u8]) -> BytesMut { + // Target Address should be sent with the first packet together, + // which would prevent from being detected. + + let addr_length = addr.serialized_len(); + let mut buffer = BytesMut::new(); + + #[cfg(feature = "aead-cipher-2022")] + if method.is_aead_2022() { + // TCP Request Header + // + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + // | TYPE | UNIX TIMESTAMP | + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + // | ADDR (Variable ...) + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + // | PADDING SIZE | PADDING (Variable ...) + // +-------+-------+-------+-------+-------+-------+-------+-------+-------+ + // + // Client -> Server TYPE=0 + + use rand::{rngs::SmallRng, Rng, SeedableRng}; + use std::{cell::RefCell, time::SystemTime}; + + const CLIENT_STREAM_TYPE: u8 = 0; + const MAX_PADDING_SIZE: usize = 900; + + thread_local! { + static PADDING_RNG: RefCell = RefCell::new(SmallRng::from_entropy()); + } + + let padding_size = if buf.is_empty() { + PADDING_RNG.with(|rng| rng.borrow_mut().gen::() % MAX_PADDING_SIZE) + } else { + // If handshake with data buffer, then padding is not required and should be 0 for letting TFO work properly. + 0 + }; + + buffer.reserve(1 + 8 + addr_length + 2 + padding_size); + buffer.put_u8(CLIENT_STREAM_TYPE); + + let timestamp = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_secs(), + Err(_) => panic!("SystemTime::now() is before UNIX Epoch!"), + }; + buffer.put_u64(timestamp); + + addr.write_to_buf(&mut buffer); + + buffer.put_u16(padding_size as u16); + + if padding_size > 0 { + let mut padding = vec![0u8; padding_size]; + PADDING_RNG.with(|rng| { + rng.borrow_mut().fill(padding.as_mut_slice()); + }); + buffer.put_slice(&padding); + } + } + + // STREAM / AEAD protocol, append the Address before payload + if buffer.is_empty() { + buffer.reserve(addr_length + buf.len()); + addr.write_to_buf(&mut buffer); + } + + buffer.put_slice(buf); + + buffer } -impl AsyncWrite for ProxyClientStreamWriteHalf +impl AsyncWrite for ProxyClientStream where - S: AsyncWrite + Unpin, + S: AsyncRead + AsyncWrite + Unpin, { fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - let mut this = self.project(); + let this = self.project(); loop { - match this.state { + match this.writer_state { ProxyClientStreamWriteState::Connect(ref addr) => { - // Target Address should be sent with the first packet together, - // which would prevent from being detected by connection features. - - let addr_length = addr.serialized_len(); - - let mut buffer = BytesMut::with_capacity(addr_length + buf.len()); - addr.write_to_buf(&mut buffer); - buffer.put_slice(buf); + let buffer = make_first_packet_buffer(this.stream.method(), addr, buf); // Save the concatenated buffer before it is written successfully. // APIs require buffer to be kept alive before Poll::Ready // // Proactor APIs like IOCP on Windows, pointers of buffers have to be kept alive // before IO completion. - *(this.state) = ProxyClientStreamWriteState::Connecting(buffer); + *(this.writer_state) = ProxyClientStreamWriteState::Connecting(buffer); } ProxyClientStreamWriteState::Connecting(ref buffer) => { - let n = ready!(this.writer.poll_write_encrypted(cx, buffer))?; + let n = ready!(this.stream.poll_write_encrypted(cx, buffer))?; // In general, poll_write_encrypted should perform like write_all. debug_assert!(n == buffer.len()); - *(this.state) = ProxyClientStreamWriteState::Connected; + *(this.writer_state) = ProxyClientStreamWriteState::Connected; // NOTE: // poll_write will return Ok(0) if buf.len() == 0 @@ -334,7 +401,7 @@ where return Ok(buf.len()).into(); } ProxyClientStreamWriteState::Connected => { - return this.writer.poll_write_encrypted(cx, buf); + return this.stream.poll_write_encrypted(cx, buf); } } } @@ -342,11 +409,11 @@ where #[inline] fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().writer.poll_flush(cx) + self.project().stream.poll_flush(cx) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().writer.poll_shutdown(cx) + self.project().stream.poll_shutdown(cx) } } diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/mod.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/mod.rs index b9715785d5ab..de8d20a3fd79 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/mod.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/mod.rs @@ -1,9 +1,12 @@ //! Stream interface for communicating with shadowsocks proxy servers -pub use self::{ - client::{ProxyClientStream, ProxyClientStreamReadHalf, ProxyClientStreamWriteHalf}, - server::{ProxyServerStream, ProxyServerStreamReadHalf, ProxyServerStreamWriteHalf}, -}; +// pub use self::{ +// client::{ProxyClientStream, ProxyClientStreamReadHalf, ProxyClientStreamWriteHalf}, +// server::{ProxyServerStream, ProxyServerStreamReadHalf, ProxyServerStreamWriteHalf}, +// }; + +pub use self::{client::ProxyClientStream, server::ProxyServerStream}; pub mod client; +pub mod protocol; pub mod server; diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/mod.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/mod.rs new file mode 100644 index 000000000000..dffa4b327fef --- /dev/null +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/mod.rs @@ -0,0 +1,173 @@ +//! Shadowsocks TCP protocol + +use std::io; + +use bytes::BufMut; +use tokio::io::AsyncRead; + +use crate::{ + crypto::{CipherCategory, CipherKind}, + relay::socks5::Address, +}; + +pub use self::v1::{ + StreamTcpRequestHeader, + StreamTcpRequestHeaderRef, + StreamTcpResponseHeader, + StreamTcpResponseHeaderRef, +}; +#[cfg(feature = "aead-cipher-2022")] +pub use self::v2::{ + Aead2022TcpRequestHeader, + Aead2022TcpRequestHeaderRef, + Aead2022TcpResponseHeader, + Aead2022TcpResponseHeaderRef, +}; + +pub mod v1; +#[cfg(feature = "aead-cipher-2022")] +pub mod v2; + +pub enum TcpRequestHeader { + Stream(StreamTcpRequestHeader), + #[cfg(feature = "aead-cipher-2022")] + Aead2022(Aead2022TcpRequestHeader), +} + +impl TcpRequestHeader { + pub async fn read_from(method: CipherKind, reader: &mut R) -> io::Result { + match method.category() { + CipherCategory::None | CipherCategory::Aead => Ok(TcpRequestHeader::Stream( + StreamTcpRequestHeader::read_from(reader).await?, + )), + #[cfg(feature = "stream-cipher")] + CipherCategory::Stream => Ok(TcpRequestHeader::Stream( + StreamTcpRequestHeader::read_from(reader).await?, + )), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => Ok(TcpRequestHeader::Aead2022( + Aead2022TcpRequestHeader::read_from(reader).await?, + )), + } + } + + pub fn write_to_buf(&self, buf: &mut B) { + match *self { + TcpRequestHeader::Stream(ref h) => h.write_to_buf(buf), + #[cfg(feature = "aead-cipher-2022")] + TcpRequestHeader::Aead2022(ref h) => h.write_to_buf(buf), + } + } + + pub fn addr(self) -> Address { + match self { + TcpRequestHeader::Stream(h) => h.addr, + #[cfg(feature = "aead-cipher-2022")] + TcpRequestHeader::Aead2022(h) => h.addr, + } + } + + pub fn addr_ref(&self) -> &Address { + match *self { + TcpRequestHeader::Stream(ref h) => &h.addr, + #[cfg(feature = "aead-cipher-2022")] + TcpRequestHeader::Aead2022(ref h) => &h.addr, + } + } + + pub fn serialized_len(&self) -> usize { + match *self { + TcpRequestHeader::Stream(ref h) => h.serialized_len(), + #[cfg(feature = "aead-cipher-2022")] + TcpRequestHeader::Aead2022(ref h) => h.serialized_len(), + } + } +} + +pub enum TcpRequestHeaderRef<'a> { + Stream(StreamTcpRequestHeaderRef<'a>), + #[cfg(feature = "aead-cipher-2022")] + Aead2022(Aead2022TcpRequestHeaderRef<'a>), +} + +impl<'a> TcpRequestHeaderRef<'a> { + pub fn write_to_buf(&self, buf: &mut B) { + match *self { + TcpRequestHeaderRef::Stream(ref h) => h.write_to_buf(buf), + #[cfg(feature = "aead-cipher-2022")] + TcpRequestHeaderRef::Aead2022(ref h) => h.write_to_buf(buf), + } + } + + pub fn serialized_len(&self) -> usize { + match *self { + TcpRequestHeaderRef::Stream(ref h) => h.serialized_len(), + #[cfg(feature = "aead-cipher-2022")] + TcpRequestHeaderRef::Aead2022(ref h) => h.serialized_len(), + } + } +} + +pub enum TcpResponseHeader { + Stream(StreamTcpResponseHeader), + #[cfg(feature = "aead-cipher-2022")] + Aead2022(Aead2022TcpResponseHeader), +} + +impl TcpResponseHeader { + pub async fn read_from(method: CipherKind, reader: &mut R) -> io::Result { + match method.category() { + CipherCategory::None | CipherCategory::Aead => Ok(TcpResponseHeader::Stream( + StreamTcpResponseHeader::read_from(reader).await?, + )), + #[cfg(feature = "stream-cipher")] + CipherCategory::Stream => Ok(TcpResponseHeader::Stream( + StreamTcpResponseHeader::read_from(reader).await?, + )), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => Ok(TcpResponseHeader::Aead2022( + Aead2022TcpResponseHeader::read_from(method, reader).await?, + )), + } + } + + pub fn write_to_buf(&self, buf: &mut B) { + match *self { + TcpResponseHeader::Stream(ref h) => h.write_to_buf(buf), + #[cfg(feature = "aead-cipher-2022")] + TcpResponseHeader::Aead2022(ref h) => h.write_to_buf(buf), + } + } + + pub fn serialized_len(&self) -> usize { + match *self { + TcpResponseHeader::Stream(ref h) => h.serialized_len(), + #[cfg(feature = "aead-cipher-2022")] + TcpResponseHeader::Aead2022(ref h) => h.serialized_len(), + } + } +} + +pub enum TcpResponseHeaderRef<'a> { + Stream(StreamTcpResponseHeaderRef<'a>), + #[cfg(feature = "aead-cipher-2022")] + Aead2022(Aead2022TcpResponseHeaderRef<'a>), +} + +impl<'a> TcpResponseHeaderRef<'a> { + pub fn write_to_buf(&self, buf: &mut B) { + match *self { + TcpResponseHeaderRef::Stream(ref h) => h.write_to_buf(buf), + #[cfg(feature = "aead-cipher-2022")] + TcpResponseHeaderRef::Aead2022(ref h) => h.write_to_buf(buf), + } + } + + pub fn serialized_len(&self) -> usize { + match *self { + TcpResponseHeaderRef::Stream(ref h) => h.serialized_len(), + #[cfg(feature = "aead-cipher-2022")] + TcpResponseHeaderRef::Aead2022(ref h) => h.serialized_len(), + } + } +} diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/v1.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/v1.rs new file mode 100644 index 000000000000..11fa020ac03d --- /dev/null +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/v1.rs @@ -0,0 +1,70 @@ +//! Shadowsocks Stream / AEAD header protocol + +use std::{io, marker::PhantomData}; + +use bytes::BufMut; +use tokio::io::AsyncRead; + +use crate::relay::socks5::Address; + +pub struct StreamTcpRequestHeader { + pub addr: Address, +} + +impl StreamTcpRequestHeader { + pub async fn read_from(reader: &mut R) -> io::Result { + Ok(StreamTcpRequestHeader { + addr: Address::read_from(reader).await?, + }) + } + + pub fn write_to_buf(&self, buf: &mut B) { + StreamTcpRequestHeaderRef { addr: &self.addr }.write_to_buf(buf) + } + + pub fn serialized_len(&self) -> usize { + StreamTcpRequestHeaderRef { addr: &self.addr }.serialized_len() + } +} + +pub struct StreamTcpRequestHeaderRef<'a> { + pub addr: &'a Address, +} + +impl<'a> StreamTcpRequestHeaderRef<'a> { + pub fn write_to_buf(&self, buf: &mut B) { + self.addr.write_to_buf(buf); + } + + pub fn serialized_len(&self) -> usize { + self.addr.serialized_len() + } +} + +pub struct StreamTcpResponseHeader; + +impl StreamTcpResponseHeader { + pub async fn read_from(_reader: &mut R) -> io::Result { + Ok(StreamTcpResponseHeader) + } + + pub fn write_to_buf(&self, buf: &mut B) { + StreamTcpResponseHeaderRef { data: PhantomData }.write_to_buf(buf) + } + + pub fn serialized_len(&self) -> usize { + 0 + } +} + +pub struct StreamTcpResponseHeaderRef<'a> { + data: PhantomData<&'a ()>, +} + +impl<'a> StreamTcpResponseHeaderRef<'a> { + pub fn write_to_buf(&self, _buf: &mut B) {} + + pub fn serialized_len(&self) -> usize { + 0 + } +} diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/v2.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/v2.rs new file mode 100644 index 000000000000..3a62be167678 --- /dev/null +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/protocol/v2.rs @@ -0,0 +1,228 @@ +//! Shadowsocks AEAD 2022 header protocol + +use std::{ + io::{self, ErrorKind}, + time::SystemTime, +}; + +use bytes::BufMut; +use tokio::io::{AsyncRead, AsyncReadExt}; + +use crate::{crypto::CipherKind, relay::Address}; + +pub const MAX_PADDING_SIZE: usize = 900; +pub const SERVER_STREAM_TIMESTAMP_MAX_DIFF: u64 = 30; + +#[inline] +pub fn get_now_timestamp() -> u64 { + match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_secs(), + Err(_) => panic!("SystemTime::now() is before UNIX Epoch!"), + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Aead2022TcpStreamType { + Client = 0, + Server = 1, +} + +/// TCP Request Header +// +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +/// | TYPE | UNIX TIMESTAMP | +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +/// | ADDR (Variable ...) +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +/// | PADDING SIZE | PADDING (Variable ...) +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +#[derive(Debug, Clone)] +pub struct Aead2022TcpRequestHeader { + pub addr: Address, + pub timestamp: u64, + pub padding: Vec, +} + +impl Aead2022TcpRequestHeader { + pub async fn read_from(reader: &mut R) -> io::Result { + use std::slice; + + let mut fix_header1 = [0u8; 1 + 8]; + reader.read_exact(&mut fix_header1).await?; + + let stream_type = fix_header1[0]; + if stream_type != Aead2022TcpStreamType::Client as u8 { + return Err(io::Error::new( + ErrorKind::Other, + format!("TCP request type {} invalid", stream_type), + )); + } + + let timestamp_slice = &fix_header1[1..]; + let timestamp_buffer: &[u64] = unsafe { slice::from_raw_parts(timestamp_slice.as_ptr() as *const _, 1) }; + let timestamp = u64::from_be(timestamp_buffer[0]); + + let now = get_now_timestamp(); + + if timestamp > now || now - timestamp > SERVER_STREAM_TIMESTAMP_MAX_DIFF { + return Err(io::Error::new( + ErrorKind::Other, + format!("received TCP request header with aged timestamp: {}", timestamp), + )) + .into(); + } + + let addr = Address::read_from(reader).await?; + + let mut padding_size_buffer = [0u8; 2]; + reader.read_exact(&mut padding_size_buffer).await?; + + let padding_size = u16::from_be_bytes(padding_size_buffer) as usize; + let mut padding = vec![0u8; padding_size]; + if padding_size > 0 { + reader.read_exact(&mut padding).await?; + } + + Ok(Aead2022TcpRequestHeader { + addr, + timestamp, + padding, + }) + } + + pub fn write_to_buf(&self, buf: &mut B) { + Aead2022TcpRequestHeaderRef { + addr: &self.addr, + timestamp: self.timestamp, + padding: &self.padding, + } + .write_to_buf(buf) + } + + pub fn serialized_len(&self) -> usize { + Aead2022TcpRequestHeaderRef { + addr: &self.addr, + timestamp: self.timestamp, + padding: &self.padding, + } + .serialized_len() + } +} + +pub struct Aead2022TcpRequestHeaderRef<'a> { + pub addr: &'a Address, + pub timestamp: u64, + pub padding: &'a [u8], +} + +impl<'a> Aead2022TcpRequestHeaderRef<'a> { + pub fn write_to_buf(&self, buf: &mut B) { + buf.put_u8(Aead2022TcpStreamType::Client as u8); + buf.put_u64(self.timestamp); + self.addr.write_to_buf(buf); + + assert!( + self.padding.len() <= MAX_PADDING_SIZE, + "padding length must be in [0, {}]", + MAX_PADDING_SIZE + ); + + buf.put_u16(self.padding.len() as u16); + if !self.padding.is_empty() { + buf.put_slice(self.padding); + } + } + + pub fn serialized_len(&self) -> usize { + 1 + 8 + self.addr.serialized_len() + 2 + self.padding.len() + } +} + +/// AEAD 2022 TCP Response Header +/// +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +/// | TYPE | UNIX TIMESTAMP | +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +/// | Request SALT (Variable ...) +/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+ +pub struct Aead2022TcpResponseHeader { + pub timestamp: u64, + pub request_salt: Vec, +} + +impl Aead2022TcpResponseHeader { + pub async fn read_from( + method: CipherKind, + reader: &mut R, + ) -> io::Result { + use std::slice; + + let mut fix_header1 = [0u8; 1 + 8]; + reader.read_exact(&mut fix_header1).await?; + + let stream_type = fix_header1[0]; + if stream_type != Aead2022TcpStreamType::Server as u8 { + return Err(io::Error::new( + ErrorKind::Other, + format!("TCP request type {} invalid", stream_type), + )); + } + + let timestamp_slice = &fix_header1[1..]; + let timestamp_buffer: &[u64] = unsafe { slice::from_raw_parts(timestamp_slice.as_ptr() as *const _, 1) }; + let timestamp = u64::from_be(timestamp_buffer[0]); + + let now = get_now_timestamp(); + + if timestamp > now || now - timestamp > SERVER_STREAM_TIMESTAMP_MAX_DIFF { + return Err(io::Error::new( + ErrorKind::Other, + format!("received TCP response header with aged timestamp: {}", timestamp), + )) + .into(); + } + + let salt_size = method.salt_len(); + let mut request_salt = vec![0u8; salt_size]; + reader.read_exact(&mut request_salt).await?; + + Ok(Aead2022TcpResponseHeader { + timestamp, + request_salt, + }) + } + + pub fn write_to_buf(&self, buf: &mut B) { + Aead2022TcpResponseHeaderRef { + timestamp: self.timestamp, + request_salt: &self.request_salt, + } + .write_to_buf(buf) + } + + pub fn serialized_len(&self) -> usize { + Aead2022TcpResponseHeaderRef { + timestamp: self.timestamp, + request_salt: &self.request_salt, + } + .serialized_len() + } +} + +pub struct Aead2022TcpResponseHeaderRef<'a> { + pub timestamp: u64, + pub request_salt: &'a [u8], +} + +impl<'a> Aead2022TcpResponseHeaderRef<'a> { + pub fn write_to_buf(&self, buf: &mut B) { + buf.put_u8(Aead2022TcpStreamType::Server as u8); + buf.put_u64(self.timestamp); + buf.put_slice(self.request_salt); + } + + pub fn serialized_len(&self) -> usize { + 1 + 8 + self.request_salt.len() + } +} diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs index e9788d675d5d..6f0fb8a40efa 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/server.rs @@ -6,21 +6,38 @@ use std::{ task::{self, Poll}, }; +use bytes::BytesMut; +use futures::ready; use pin_project::pin_project; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::{ context::SharedContext, - crypto::v1::CipherKind, - relay::tcprelay::crypto_io::{CryptoStream, CryptoStreamReadHalf, CryptoStreamWriteHalf}, + crypto::CipherKind, + relay::{ + socks5::Address, + tcprelay::{ + crypto_io::{CryptoRead, CryptoStream, CryptoWrite}, + proxy_stream::protocol::TcpRequestHeader, + }, + }, }; +enum ProxyServerStreamWriteState { + #[cfg(feature = "aead-cipher-2022")] + PrepareHeader(Option), + #[cfg(feature = "aead-cipher-2022")] + WriteHeader(BytesMut, usize), + Established, +} + /// A stream for communicating with shadowsocks' proxy client #[pin_project] pub struct ProxyServerStream { #[pin] stream: CryptoStream, context: SharedContext, + writer_state: ProxyServerStreamWriteState, } impl ProxyServerStream { @@ -30,9 +47,20 @@ impl ProxyServerStream { method: CipherKind, key: &[u8], ) -> ProxyServerStream { + #[cfg(feature = "aead-cipher-2022")] + let writer_state = if method.is_aead_2022() { + ProxyServerStreamWriteState::PrepareHeader(None) + } else { + ProxyServerStreamWriteState::Established + }; + + #[cfg(not(feature = "aead-cipher-2022"))] + let writer_state = ProxyServerStreamWriteState::Established; + ProxyServerStream { stream: CryptoStream::from_stream(&context, stream, method, key), context, + writer_state, } } @@ -56,17 +84,13 @@ impl ProxyServerStream where S: AsyncRead + AsyncWrite + Unpin, { - /// Splits into reader and writer halves - pub fn into_split(self) -> (ProxyServerStreamReadHalf, ProxyServerStreamWriteHalf) { - let (reader, writer) = self.stream.into_split(); - - ( - ProxyServerStreamReadHalf { - reader, - context: self.context, - }, - ProxyServerStreamWriteHalf { writer }, - ) + /// Handshaking. Getting the destination address from client + /// + /// This method should be called only once after accepted. + pub async fn handshake(&mut self) -> io::Result
{ + let header = TcpRequestHeader::read_from(self.stream.method(), self).await?; + // TODO: Check header is not in a standalone AEAD package + Ok(header.addr()) } } @@ -76,8 +100,18 @@ where { #[inline] fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - let mut this = self.project(); - this.stream.poll_read_decrypted(cx, this.context, buf) + let this = self.project(); + ready!(this.stream.poll_read_decrypted(cx, this.context, buf))?; + + // Wakeup writer task because we have already received the salt + #[cfg(feature = "aead-cipher-2022")] + if let ProxyServerStreamWriteState::PrepareHeader(ref mut waker) = this.writer_state { + if let Some(waker) = waker.take() { + waker.wake(); + } + } + + Ok(()).into() } } @@ -87,62 +121,64 @@ where { #[inline] fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - self.project().stream.poll_write_encrypted(cx, buf) - } - - #[inline] - fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().stream.poll_flush(cx) - } - - #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().stream.poll_shutdown(cx) - } -} - -/// Owned read half produced by `ProxyServerStream::into_split` -#[pin_project] -pub struct ProxyServerStreamReadHalf { - #[pin] - reader: CryptoStreamReadHalf, - context: SharedContext, -} - -impl AsyncRead for ProxyServerStreamReadHalf -where - S: AsyncRead + Unpin, -{ - #[inline] - fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { let mut this = self.project(); - this.reader.poll_read_decrypted(cx, this.context, buf) - } -} -/// Owned write half produced by `ProxyServerStream::into_split` -#[pin_project] -pub struct ProxyServerStreamWriteHalf { - #[pin] - writer: CryptoStreamWriteHalf, -} - -impl AsyncWrite for ProxyServerStreamWriteHalf -where - S: AsyncWrite + Unpin, -{ - #[inline] - fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll> { - self.project().writer.poll_write_encrypted(cx, buf) + loop { + match *this.writer_state { + ProxyServerStreamWriteState::Established => { + return this.stream.poll_write_encrypted(cx, buf); + } + #[cfg(feature = "aead-cipher-2022")] + ProxyServerStreamWriteState::PrepareHeader(ref mut waker) => { + match this.stream.received_nonce() { + None => { + // Reader didn't receive the salt from client yet. + if let Some(waker) = waker.take() { + if !waker.will_wake(cx.waker()) { + waker.wake(); + } + } + *waker = Some(cx.waker().clone()); + return Poll::Pending; + } + Some(nonce) => { + use crate::relay::tcprelay::proxy_stream::protocol::v2::{ + get_now_timestamp, + Aead2022TcpResponseHeaderRef, + }; + + let header = Aead2022TcpResponseHeaderRef { + timestamp: get_now_timestamp(), + request_salt: nonce, + }; + + let mut buffer = BytesMut::with_capacity(header.serialized_len()); + header.write_to_buf(&mut buffer); + + *(this.writer_state) = ProxyServerStreamWriteState::WriteHeader(buffer, 0); + } + } + } + #[cfg(feature = "aead-cipher-2022")] + ProxyServerStreamWriteState::WriteHeader(ref buf, ref mut buf_pos) => { + let n = ready!(this.stream.as_mut().poll_write_encrypted(cx, &buf[*buf_pos..]))?; + *buf_pos += n; + + if *buf_pos >= buf.len() { + *(this.writer_state) = ProxyServerStreamWriteState::Established; + } + } + } + } } #[inline] fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().writer.poll_flush(cx) + self.project().stream.poll_flush(cx) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().writer.poll_shutdown(cx) + self.project().stream.poll_shutdown(cx) } } diff --git a/crates/shadowsocks/src/relay/tcprelay/stream.rs b/crates/shadowsocks/src/relay/tcprelay/stream.rs index ff48438f3a2b..e91f3d7d0a1e 100644 --- a/crates/shadowsocks/src/relay/tcprelay/stream.rs +++ b/crates/shadowsocks/src/relay/tcprelay/stream.rs @@ -15,7 +15,7 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::{ context::Context, - crypto::v1::{Cipher, CipherKind}, + crypto::{v1::Cipher, CipherKind}, }; enum DecryptReadState { @@ -29,6 +29,7 @@ pub struct DecryptedReader { cipher: Option, buffer: BytesMut, method: CipherKind, + iv: Option, } impl DecryptedReader { @@ -41,6 +42,7 @@ impl DecryptedReader { cipher: None, buffer: BytesMut::with_capacity(method.iv_len()), method, + iv: None, } } else { DecryptedReader { @@ -48,10 +50,15 @@ impl DecryptedReader { cipher: Some(Cipher::new(method, key, &[])), buffer: BytesMut::new(), method, + iv: Some(Bytes::new()), } } } + pub fn iv(&self) -> Option<&[u8]> { + self.iv.as_deref() + } + /// Attempt to read decrypted data from reader pub fn poll_read_decrypted( &mut self, @@ -116,8 +123,10 @@ impl DecryptedReader { trace!("got stream iv {:?}", ByteStr::new(iv)); - let cipher = Cipher::new(self.method, key, iv); + // Stores IV + self.iv = Some(Bytes::copy_from_slice(iv)); + let cipher = Cipher::new(self.method, key, iv); self.cipher = Some(cipher); Ok(()).into() @@ -165,6 +174,7 @@ pub struct EncryptedWriter { cipher: Cipher, buffer: BytesMut, state: EncryptWriteState, + iv: Bytes, } impl EncryptedWriter { @@ -178,9 +188,15 @@ impl EncryptedWriter { cipher: Cipher::new(method, key, nonce), buffer, state: EncryptWriteState::AssemblePacket, + iv: Bytes::copy_from_slice(nonce), } } + /// IV + pub fn iv(&self) -> &[u8] { + self.iv.as_ref() + } + /// Attempt to write encrypted data into the writer pub fn poll_write_encrypted( &mut self, diff --git a/crates/shadowsocks/src/relay/tcprelay/utils.rs b/crates/shadowsocks/src/relay/tcprelay/utils.rs index f01b96b78f05..252ab223bd20 100644 --- a/crates/shadowsocks/src/relay/tcprelay/utils.rs +++ b/crates/shadowsocks/src/relay/tcprelay/utils.rs @@ -14,7 +14,7 @@ use futures::ready; use pin_project::pin_project; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use crate::crypto::v1::{CipherCategory, CipherKind}; +use crate::crypto::{CipherCategory, CipherKind}; #[derive(Debug)] struct CopyBuffer { @@ -146,6 +146,8 @@ fn encrypted_read_buffer_size(method: CipherKind) -> usize { #[cfg(feature = "stream-cipher")] CipherCategory::Stream => 1 << 14, CipherCategory::None => 1 << 14, + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => super::aead_2022::MAX_PACKET_SIZE + method.tag_len(), } } @@ -155,6 +157,8 @@ fn plain_read_buffer_size(method: CipherKind) -> usize { #[cfg(feature = "stream-cipher")] CipherCategory::Stream => 1 << 14, CipherCategory::None => 1 << 14, + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => super::aead_2022::MAX_PACKET_SIZE, } } diff --git a/crates/shadowsocks/src/relay/udprelay/aead.rs b/crates/shadowsocks/src/relay/udprelay/aead.rs new file mode 100644 index 000000000000..7a8ecb8ff2b9 --- /dev/null +++ b/crates/shadowsocks/src/relay/udprelay/aead.rs @@ -0,0 +1,121 @@ +//! Shadowsocks UDP AEAD protocol +//! +//! Payload with AEAD cipher +//! +//! ```plain +//! UDP (after encryption, *ciphertext*) +//! +--------+-----------+-----------+ +//! | NONCE | *Data* | Data_TAG | +//! +--------+-----------+-----------+ +//! | Fixed | Variable | Fixed | +//! +--------+-----------+-----------+ +//! ``` + +use std::io::{self, Cursor, ErrorKind}; + +use byte_string::ByteStr; +use bytes::{BufMut, BytesMut}; +use log::trace; + +use crate::{ + context::Context, + crypto::{v1::Cipher, CipherKind}, + relay::socks5::Address, +}; + +/// Encrypt UDP AEAD protocol packet +pub fn encrypt_payload_aead( + context: &Context, + method: CipherKind, + key: &[u8], + addr: &Address, + payload: &[u8], + dst: &mut BytesMut, +) { + let salt_len = method.salt_len(); + let addr_len = addr.serialized_len(); + + // Packet = IV + ADDRESS + PAYLOAD + TAG + dst.reserve(salt_len + addr_len + payload.len() + method.tag_len()); + + // Generate IV + dst.resize(salt_len, 0); + let salt = &mut dst[..salt_len]; + + if salt_len > 0 { + context.generate_nonce(salt, false); + trace!("UDP packet generated aead salt {:?}", ByteStr::new(salt)); + } + + let mut cipher = Cipher::new(method, key, salt); + + addr.write_to_buf(dst); + dst.put_slice(payload); + + dst.reserve(method.tag_len()); + unsafe { + dst.advance_mut(method.tag_len()); + } + + let m = &mut dst[salt_len..]; + cipher.encrypt_packet(m); +} + +/// Decrypt UDP AEAD protocol packet +pub async fn decrypt_payload_aead( + _context: &Context, + method: CipherKind, + key: &[u8], + payload: &mut [u8], +) -> io::Result<(usize, Address)> { + let plen = payload.len(); + let salt_len = method.salt_len(); + if plen < salt_len { + let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short for salt"); + return Err(err); + } + + let (salt, data) = payload.split_at_mut(salt_len); + // context.check_nonce_replay(salt)?; + + trace!("UDP packet got AEAD salt {:?}", ByteStr::new(salt)); + + let mut cipher = Cipher::new(method, key, salt); + let tag_len = cipher.tag_len(); + + if data.len() < tag_len { + return Err(io::Error::new(io::ErrorKind::Other, "udp packet too short for tag")); + } + + if !cipher.decrypt_packet(data) { + return Err(io::Error::new(io::ErrorKind::Other, "invalid tag-in")); + } + + // Truncate TAG + let data_len = data.len() - tag_len; + let data = &mut data[..data_len]; + + let (dn, addr) = parse_packet(data).await?; + + let data_length = data_len - dn; + let data_start_idx = salt_len + dn; + let data_end_idx = data_start_idx + data_length; + + payload.copy_within(data_start_idx..data_end_idx, 0); + + Ok((data_length, addr)) +} + +async fn parse_packet(buf: &[u8]) -> io::Result<(usize, Address)> { + let mut cur = Cursor::new(buf); + match Address::read_from(&mut cur).await { + Ok(address) => { + let pos = cur.position() as usize; + Ok((pos, address)) + } + Err(..) => { + let err = io::Error::new(ErrorKind::InvalidData, "parse udp packet Address failed"); + Err(err) + } + } +} diff --git a/crates/shadowsocks/src/relay/udprelay/aead_2022.rs b/crates/shadowsocks/src/relay/udprelay/aead_2022.rs new file mode 100644 index 000000000000..d4d4d57d0a78 --- /dev/null +++ b/crates/shadowsocks/src/relay/udprelay/aead_2022.rs @@ -0,0 +1,438 @@ +//! Shadowsocks UDP AEAD 2022 protocol +//! +//! Payload with AEAD 2022 cipher +//! +//! Client -> Server +//! +//! ```plain +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Client Session ID | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Packet ID | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | TYPE | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | UNIX Epoch Timestamp | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | PADDING SIZE | Padding (Variable ...) +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Address (Variable ...) +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Payload (Variable ...) +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! ``` +//! +//! Server -> Client +//! +//! ```plain +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Server Session ID | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Packet ID | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | TYPE | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | UNIX Epoch Timestamp | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Client Session ID | +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | PADDING SIZE | Padding (Variable ...) +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Address (Variable ...) +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! | Payload (Variable ...) +//! +-------+-------+-------+-------+-------+-------+-------+-------+ +//! ``` + +use std::{ + cell::RefCell, + io::{self, Cursor, ErrorKind, Seek, SeekFrom}, + slice, + time::SystemTime, +}; + +use aes::{ + cipher::{BlockDecrypt, BlockEncrypt, KeyInit}, + Aes128, + Aes256, + Block, +}; +use byte_string::ByteStr; +use bytes::{Buf, BufMut, BytesMut}; +use log::trace; +use rand::{rngs::SmallRng, Rng, SeedableRng}; + +use crate::{ + context::Context, + crypto::{ + v2::udp::{ChaCha20Poly1305Cipher, UdpCipher}, + CipherKind, + }, + relay::socks5::Address, +}; + +use super::options::UdpSocketControlData; + +const CLIENT_SOCKET_TYPE: u8 = 0; +const SERVER_SOCKET_TYPE: u8 = 1; +const MAX_PADDING_SIZE: usize = 900; +const SERVER_PACKET_TIMESTAMP_MAX_DIFF: u64 = 30; + +thread_local! { + static PADDING_RNG: RefCell = RefCell::new(SmallRng::from_entropy()); +} + +#[inline] +fn get_padding_size(payload: &[u8]) -> usize { + if payload.is_empty() { + PADDING_RNG.with(|rng| rng.borrow_mut().gen::() % MAX_PADDING_SIZE) + } else { + 0 + } +} + +#[inline] +fn fill_padding(padding: &mut [u8]) { + if padding.is_empty() { + return; + } + + PADDING_RNG.with(|rng| rng.borrow_mut().fill(padding)); +} + +#[inline] +pub fn get_now_timestamp() -> u64 { + match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_secs(), + Err(_) => panic!("SystemTime::now() is before UNIX Epoch!"), + } +} + +fn encrypt_message(method: CipherKind, key: &[u8], packet: &mut BytesMut, session_id: u64) { + packet.reserve(method.tag_len()); + unsafe { + packet.advance_mut(method.tag_len()); + } + + match method { + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => { + // ChaCha20-Poly1305 uses PSK as key, prepended nonce in packet + let nonce_size = ChaCha20Poly1305Cipher::nonce_size(); + + let mut cipher = { + let nonce = &packet[..nonce_size]; + UdpCipher::new(method, key, nonce, session_id) + }; + + cipher.encrypt_packet(&mut packet[nonce_size..]); + } + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + // AES-*-GCM uses derived key, and part of the packet header as nonce + + let mut cipher = { + let nonce = &packet[4..16]; + UdpCipher::new(method, key, nonce, session_id) + }; + + // [SessionID + PacketID] is encrypted with AES-ECB with PSK + // No padding is required because these 2 fields are 128-bits, which is exactly the same as AES's block size + match method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { + let cipher = Aes128::new_from_slice(key).expect("AES-128 init"); + let block = Block::from_mut_slice(&mut packet[0..16]); + cipher.encrypt_block(block); + } + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + let cipher = Aes256::new_from_slice(key).expect("AES-256 init"); + let block = Block::from_mut_slice(&mut packet[0..16]); + cipher.encrypt_block(block); + } + _ => unreachable!("{} is not an AES-*-GCM cipher", method), + } + + // Encrypt the rest of the packet with AEAD cipher (AES-*-GCM) + cipher.encrypt_packet(&mut packet[16..]); + } + _ => unreachable!("{} is not an AEAD 2022 cipher", method), + } +} + +fn decrypt_message(method: CipherKind, key: &[u8], packet: &mut [u8]) -> bool { + match method { + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => { + // ChaCha20-Poly1305 uses PSK as key, prepended nonce in packet + let nonce_size = ChaCha20Poly1305Cipher::nonce_size(); + + let mut cipher = { + let nonce = &packet[..nonce_size]; + // NOTE: ChaCha20-Poly1305's session_id is not required because it uses PSK directly + UdpCipher::new(method, key, nonce, 0) + }; + + if !cipher.decrypt_packet(&mut packet[nonce_size..]) { + return false; + } + } + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + // AES-*-GCM uses derived key, and part of the packet header as nonce + // + // Decrypt the header block first + // [SessionID + PacketID] is encrypted with AES-ECB with PSK + // No padding is required because these 2 fields are 128-bits, which is exactly the same as AES's block size + match method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => { + let cipher = Aes128::new_from_slice(key).expect("AES-128 init"); + let block = Block::from_mut_slice(&mut packet[0..16]); + cipher.decrypt_block(block); + } + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + let cipher = Aes256::new_from_slice(key).expect("AES-256 init"); + let block = Block::from_mut_slice(&mut packet[0..16]); + cipher.decrypt_block(block); + } + _ => unreachable!("{} is not an AES-*-GCM cipher", method), + } + + // Session ID is the first 64-bits + let session_id_buf = &packet[0..8]; + let session_id_slice: &[u64] = unsafe { slice::from_raw_parts(session_id_buf.as_ptr() as *const _, 1) }; + let session_id = u64::from_be(session_id_slice[0]); + + let mut cipher = { + let nonce = &packet[4..16]; + UdpCipher::new(method, key, nonce, session_id) + }; + + if !cipher.decrypt_packet(&mut packet[16..]) { + return false; + } + } + _ => unreachable!("{} is not an AEAD 2022 cipher", method), + } + + true +} + +#[inline] +fn get_nonce_len(method: CipherKind) -> usize { + match method { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM => 0, + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => method.nonce_len(), + _ => unreachable!("{} is not an AEAD 2022 cipher", method), + } +} + +/// Encrypt `Client -> Server` UDP AEAD protocol packet +pub fn encrypt_client_payload_aead_2022( + context: &Context, + method: CipherKind, + key: &[u8], + addr: &Address, + control: &UdpSocketControlData, + payload: &[u8], + dst: &mut BytesMut, +) { + let padding_size = get_padding_size(payload); + let nonce_size = get_nonce_len(method); + + dst.reserve( + nonce_size + 8 + 8 + 1 + 8 + 2 + padding_size + addr.serialized_len() + payload.len() + method.tag_len(), + ); + + // Generate IV + if nonce_size > 0 { + dst.resize(nonce_size, 0); + let nonce = &mut dst[..nonce_size]; + + if nonce_size > 0 { + context.generate_nonce(nonce, false); + trace!("UDP packet generated aead nonce {:?}", ByteStr::new(nonce)); + } + } + + // Add header fields + dst.put_u64(control.client_session_id); + dst.put_u64(control.packet_id); + dst.put_u8(CLIENT_SOCKET_TYPE); + dst.put_u64(get_now_timestamp()); + dst.put_u16(padding_size as u16); + if padding_size > 0 { + let mut padding = vec![0u8; padding_size]; + fill_padding(&mut padding); + dst.put_slice(&padding); + } + addr.write_to_buf(dst); + dst.put_slice(payload); + + encrypt_message(method, key, dst, control.client_session_id); +} + +/// Decrypt `Client -> Server` UDP AEAD protocol packet +pub async fn decrypt_client_payload_aead_2022( + _context: &Context, + method: CipherKind, + key: &[u8], + payload: &mut [u8], +) -> io::Result<(usize, Address, UdpSocketControlData)> { + let nonce_len = get_nonce_len(method); + let tag_len = method.tag_len(); + if payload.len() < nonce_len + tag_len + 8 + 8 + 1 + 8 + 2 { + let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short"); + return Err(err); + } + + if !decrypt_message(method, key, payload) { + return Err(io::Error::new(io::ErrorKind::Other, "invalid tag-in")); + } + + let data = &payload[nonce_len..payload.len() - tag_len]; + let mut cursor = Cursor::new(data); + + let client_session_id = cursor.get_u64(); + let packet_id = cursor.get_u64(); + let socket_type = cursor.get_u8(); + if socket_type != CLIENT_SOCKET_TYPE { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("invalid socket type {}", socket_type), + )); + } + let timestamp = cursor.get_u64(); + + let now = get_now_timestamp(); + if timestamp > now || now - timestamp > SERVER_PACKET_TIMESTAMP_MAX_DIFF { + return Err(io::Error::new( + ErrorKind::Other, + format!("received TCP response header with aged timestamp: {}", timestamp), + )); + } + + let padding_size = cursor.get_u16() as usize; + if padding_size > 0 { + cursor.seek(SeekFrom::Current(padding_size as i64))?; + } + + let control = UdpSocketControlData { + client_session_id, + server_session_id: 0, + packet_id, + }; + + let addr = Address::read_from(&mut cursor).await?; + + let payload_start = cursor.position() as usize; + let payload_len = data.len() - payload_start; + + payload.copy_within(nonce_len + payload_start..nonce_len + payload_start + payload_len, 0); + + Ok((payload_len, addr, control)) +} + +/// Encrypt `Server -> Client` UDP AEAD protocol packet +pub fn encrypt_server_payload_aead_2022( + context: &Context, + method: CipherKind, + key: &[u8], + addr: &Address, + control: &UdpSocketControlData, + payload: &[u8], + dst: &mut BytesMut, +) { + let padding_size = get_padding_size(payload); + let nonce_size = get_nonce_len(method); + + dst.reserve( + nonce_size + 8 + 8 + 1 + 8 + 2 + padding_size + addr.serialized_len() + payload.len() + method.tag_len(), + ); + + // Generate IV + if nonce_size > 0 { + dst.resize(nonce_size, 0); + let nonce = &mut dst[..nonce_size]; + + if nonce_size > 0 { + context.generate_nonce(nonce, false); + trace!("UDP packet generated aead nonce {:?}", ByteStr::new(nonce)); + } + } + + // Add header fields + dst.put_u64(control.server_session_id); + dst.put_u64(control.packet_id); + dst.put_u8(SERVER_SOCKET_TYPE); + dst.put_u64(get_now_timestamp()); + dst.put_u64(control.client_session_id); + dst.put_u16(padding_size as u16); + if padding_size > 0 { + let mut padding = vec![0u8; padding_size]; + fill_padding(&mut padding); + dst.put_slice(&padding); + } + addr.write_to_buf(dst); + dst.put_slice(payload); + + encrypt_message(method, key, dst, control.server_session_id); +} + +/// Decrypt `Server -> Client` UDP AEAD protocol packet +pub async fn decrypt_server_payload_aead_2022( + _context: &Context, + method: CipherKind, + key: &[u8], + payload: &mut [u8], +) -> io::Result<(usize, Address, UdpSocketControlData)> { + let nonce_len = get_nonce_len(method); + let tag_len = method.tag_len(); + if payload.len() < nonce_len + tag_len + 8 + 8 + 1 + 8 + 2 { + let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short"); + return Err(err); + } + + if !decrypt_message(method, key, payload) { + return Err(io::Error::new(io::ErrorKind::Other, "invalid tag-in")); + } + + let data = &payload[nonce_len..payload.len() - tag_len]; + let mut cursor = Cursor::new(data); + + let server_session_id = cursor.get_u64(); + let packet_id = cursor.get_u64(); + let socket_type = cursor.get_u8(); + if socket_type != SERVER_SOCKET_TYPE { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("invalid socket type {}", socket_type), + )); + } + let timestamp = cursor.get_u64(); + + let now = get_now_timestamp(); + if timestamp > now || now - timestamp > SERVER_PACKET_TIMESTAMP_MAX_DIFF { + return Err(io::Error::new( + ErrorKind::Other, + format!("received TCP response header with aged timestamp: {}", timestamp), + )); + } + + let client_session_id = cursor.get_u64(); + + let padding_size = cursor.get_u16() as usize; + if padding_size > 0 { + cursor.seek(SeekFrom::Current(padding_size as i64))?; + } + + let control = UdpSocketControlData { + client_session_id, + server_session_id, + packet_id, + }; + + let addr = Address::read_from(&mut cursor).await?; + + let payload_start = cursor.position() as usize; + let payload_len = data.len() - payload_start; + + payload.copy_within(nonce_len + payload_start..nonce_len + payload_start + payload_len, 0); + + Ok((payload_len, addr, control)) +} diff --git a/crates/shadowsocks/src/relay/udprelay/crypto_io.rs b/crates/shadowsocks/src/relay/udprelay/crypto_io.rs index ac231beed8d6..a22c315fb2cf 100644 --- a/crates/shadowsocks/src/relay/udprelay/crypto_io.rs +++ b/crates/shadowsocks/src/relay/udprelay/crypto_io.rs @@ -21,22 +21,35 @@ //! ``` use std::io::{self, Cursor, ErrorKind}; -use byte_string::ByteStr; use bytes::{BufMut, BytesMut}; -use log::trace; use crate::{ context::Context, - crypto::v1::{Cipher, CipherCategory, CipherKind}, + crypto::{CipherCategory, CipherKind}, relay::socks5::Address, }; -/// Encrypt payload into ShadowSocks UDP encrypted packet -pub fn encrypt_payload( +#[cfg(feature = "aead-cipher-2022")] +use super::aead_2022::{ + decrypt_client_payload_aead_2022, + decrypt_server_payload_aead_2022, + encrypt_client_payload_aead_2022, + encrypt_server_payload_aead_2022, +}; +#[cfg(feature = "stream-cipher")] +use super::stream::{decrypt_payload_stream, encrypt_payload_stream}; +use super::{ + aead::{decrypt_payload_aead, encrypt_payload_aead}, + options::UdpSocketControlData, +}; + +/// Encrypt `Client -> Server` payload into ShadowSocks UDP encrypted packet +pub fn encrypt_client_payload( context: &Context, method: CipherKind, key: &[u8], addr: &Address, + control: &UdpSocketControlData, payload: &[u8], dst: &mut BytesMut, ) { @@ -49,84 +62,42 @@ pub fn encrypt_payload( #[cfg(feature = "stream-cipher")] CipherCategory::Stream => encrypt_payload_stream(context, method, key, addr, payload, dst), CipherCategory::Aead => encrypt_payload_aead(context, method, key, addr, payload, dst), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => encrypt_client_payload_aead_2022(context, method, key, addr, control, payload, dst), } } -#[cfg(feature = "stream-cipher")] -fn encrypt_payload_stream( +/// Encrypt `Server -> Client` payload into ShadowSocks UDP encrypted packet +pub fn encrypt_server_payload( context: &Context, method: CipherKind, key: &[u8], addr: &Address, + control: &UdpSocketControlData, payload: &[u8], dst: &mut BytesMut, ) { - let iv_len = method.iv_len(); - let addr_len = addr.serialized_len(); - - // Packet = IV + ADDRESS + PAYLOAD - dst.reserve(iv_len + addr_len + payload.len()); - - // Generate IV - dst.resize(iv_len, 0); - let iv = &mut dst[..iv_len]; - - if iv_len > 0 { - context.generate_nonce(iv, false); - trace!("UDP packet generated stream iv {:?}", ByteStr::new(iv)); - } - - let mut cipher = Cipher::new(method, key, iv); - - addr.write_to_buf(dst); - dst.put_slice(payload); - let m = &mut dst[iv_len..]; - cipher.encrypt_packet(m); -} - -fn encrypt_payload_aead( - context: &Context, - method: CipherKind, - key: &[u8], - addr: &Address, - payload: &[u8], - dst: &mut BytesMut, -) { - let salt_len = method.salt_len(); - let addr_len = addr.serialized_len(); - - // Packet = IV + ADDRESS + PAYLOAD + TAG - dst.reserve(salt_len + addr_len + payload.len() + method.tag_len()); - - // Generate IV - dst.resize(salt_len, 0); - let salt = &mut dst[..salt_len]; - - if salt_len > 0 { - context.generate_nonce(salt, false); - trace!("UDP packet generated aead salt {:?}", ByteStr::new(salt)); - } - - let mut cipher = Cipher::new(method, key, salt); - - addr.write_to_buf(dst); - dst.put_slice(payload); - - unsafe { - dst.advance_mut(method.tag_len()); + match method.category() { + CipherCategory::None => { + dst.reserve(addr.serialized_len() + payload.len()); + addr.write_to_buf(dst); + dst.put_slice(payload); + } + #[cfg(feature = "stream-cipher")] + CipherCategory::Stream => encrypt_payload_stream(context, method, key, addr, payload, dst), + CipherCategory::Aead => encrypt_payload_aead(context, method, key, addr, payload, dst), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => encrypt_server_payload_aead_2022(context, method, key, addr, control, payload, dst), } - - let m = &mut dst[salt_len..]; - cipher.encrypt_packet(m); } -/// Decrypt payload from ShadowSocks UDP encrypted packet -pub async fn decrypt_payload( +/// Decrypt `Client -> Server` payload from ShadowSocks UDP encrypted packet +pub async fn decrypt_client_payload( context: &Context, method: CipherKind, key: &[u8], payload: &mut [u8], -) -> io::Result<(usize, Address)> { +) -> io::Result<(usize, Address, Option)> { match method.category() { CipherCategory::None => { let mut cur = Cursor::new(payload); @@ -135,7 +106,7 @@ pub async fn decrypt_payload( let pos = cur.position() as usize; let payload = cur.into_inner(); payload.copy_within(pos.., 0); - Ok((payload.len() - pos, address)) + Ok((payload.len() - pos, address, None)) } Err(..) => { let err = io::Error::new(ErrorKind::InvalidData, "parse udp packet Address failed"); @@ -144,97 +115,52 @@ pub async fn decrypt_payload( } } #[cfg(feature = "stream-cipher")] - CipherCategory::Stream => decrypt_payload_stream(context, method, key, payload).await, - CipherCategory::Aead => decrypt_payload_aead(context, method, key, payload).await, + CipherCategory::Stream => decrypt_payload_stream(context, method, key, payload) + .await + .map(|(n, a)| (n, a, None)), + CipherCategory::Aead => decrypt_payload_aead(context, method, key, payload) + .await + .map(|(n, a)| (n, a, None)), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => decrypt_client_payload_aead_2022(context, method, key, payload) + .await + .map(|(n, a, c)| (n, a, Some(c))), } } -#[cfg(feature = "stream-cipher")] -async fn decrypt_payload_stream( - _context: &Context, - method: CipherKind, - key: &[u8], - payload: &mut [u8], -) -> io::Result<(usize, Address)> { - let plen = payload.len(); - let iv_len = method.iv_len(); - - if plen < iv_len { - let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short for iv"); - return Err(err); - } - - let (iv, data) = payload.split_at_mut(iv_len); - // context.check_nonce_replay(iv)?; - - trace!("UDP packet got stream IV {:?}", ByteStr::new(iv)); - let mut cipher = Cipher::new(method, key, iv); - - assert!(cipher.decrypt_packet(data)); - - let (dn, addr) = parse_packet(data).await?; - - let data_start_idx = iv_len + dn; - let data_length = payload.len() - data_start_idx; - payload.copy_within(data_start_idx.., 0); - - Ok((data_length, addr)) -} - -async fn decrypt_payload_aead( - _context: &Context, +/// Decrypt `Server -> Client` payload from ShadowSocks UDP encrypted packet +pub async fn decrypt_server_payload( + context: &Context, method: CipherKind, key: &[u8], payload: &mut [u8], -) -> io::Result<(usize, Address)> { - let plen = payload.len(); - let salt_len = method.salt_len(); - if plen < salt_len { - let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short for salt"); - return Err(err); - } - - let (salt, data) = payload.split_at_mut(salt_len); - // context.check_nonce_replay(salt)?; - - trace!("UDP packet got AEAD salt {:?}", ByteStr::new(salt)); - - let mut cipher = Cipher::new(method, key, salt); - let tag_len = cipher.tag_len(); - - if data.len() < tag_len { - return Err(io::Error::new(io::ErrorKind::Other, "udp packet too short for tag")); - } - - if !cipher.decrypt_packet(data) { - return Err(io::Error::new(io::ErrorKind::Other, "invalid tag-in")); - } - - // Truncate TAG - let data_len = data.len() - tag_len; - let data = &mut data[..data_len]; - - let (dn, addr) = parse_packet(data).await?; - - let data_length = data_len - dn; - let data_start_idx = salt_len + dn; - let data_end_idx = data_start_idx + data_length; - - payload.copy_within(data_start_idx..data_end_idx, 0); - - Ok((data_length, addr)) -} - -async fn parse_packet(buf: &[u8]) -> io::Result<(usize, Address)> { - let mut cur = Cursor::new(buf); - match Address::read_from(&mut cur).await { - Ok(address) => { - let pos = cur.position() as usize; - Ok((pos, address)) - } - Err(..) => { - let err = io::Error::new(ErrorKind::InvalidData, "parse udp packet Address failed"); - Err(err) +) -> io::Result<(usize, Address, Option)> { + match method.category() { + CipherCategory::None => { + let mut cur = Cursor::new(payload); + match Address::read_from(&mut cur).await { + Ok(address) => { + let pos = cur.position() as usize; + let payload = cur.into_inner(); + payload.copy_within(pos.., 0); + Ok((payload.len() - pos, address, None)) + } + Err(..) => { + let err = io::Error::new(ErrorKind::InvalidData, "parse udp packet Address failed"); + Err(err) + } + } } + #[cfg(feature = "stream-cipher")] + CipherCategory::Stream => decrypt_payload_stream(context, method, key, payload) + .await + .map(|(n, a)| (n, a, None)), + CipherCategory::Aead => decrypt_payload_aead(context, method, key, payload) + .await + .map(|(n, a)| (n, a, None)), + #[cfg(feature = "aead-cipher-2022")] + CipherCategory::Aead2022 => decrypt_server_payload_aead_2022(context, method, key, payload) + .await + .map(|(n, a, c)| (n, a, Some(c))), } } diff --git a/crates/shadowsocks/src/relay/udprelay/mod.rs b/crates/shadowsocks/src/relay/udprelay/mod.rs index 7d17ab133c92..31c8a6c0e9f4 100644 --- a/crates/shadowsocks/src/relay/udprelay/mod.rs +++ b/crates/shadowsocks/src/relay/udprelay/mod.rs @@ -51,8 +51,14 @@ use std::time::Duration; pub use self::proxy_socket::ProxySocket; +mod aead; +#[cfg(feature = "aead-cipher-2022")] +mod aead_2022; mod crypto_io; +pub mod options; pub mod proxy_socket; +#[cfg(feature = "stream-cipher")] +mod stream; /// The maximum UDP payload size (defined in the original shadowsocks Python) /// diff --git a/crates/shadowsocks/src/relay/udprelay/options.rs b/crates/shadowsocks/src/relay/udprelay/options.rs new file mode 100644 index 000000000000..eb4d57678e3d --- /dev/null +++ b/crates/shadowsocks/src/relay/udprelay/options.rs @@ -0,0 +1,15 @@ +//! UDP Socket options and extra data + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +pub struct UdpSocketControlData { + /// Session ID in client. + /// + /// For identifying an unique association in client + pub client_session_id: u64, + /// Session ID in server. + /// + /// For identifying an unique association in server + pub server_session_id: u64, + /// Packet counter + pub packet_id: u64, +} diff --git a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs index 566a03319352..c155e8d666b4 100644 --- a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs +++ b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs @@ -13,17 +13,33 @@ use tokio::{ use crate::{ config::{ServerAddr, ServerConfig}, context::SharedContext, - crypto::v1::CipherKind, + crypto::CipherKind, net::{AcceptOpts, ConnectOpts, UdpSocket as ShadowUdpSocket}, - relay::socks5::Address, + relay::{socks5::Address, udprelay::options::UdpSocketControlData}, }; -use super::crypto_io::{decrypt_payload, encrypt_payload}; +use super::crypto_io::{ + decrypt_client_payload, + decrypt_server_payload, + encrypt_client_payload, + encrypt_server_payload, +}; static DEFAULT_CONNECT_OPTS: Lazy = Lazy::new(Default::default); +static DEFAULT_SOCKET_CONTROL: Lazy = Lazy::new(UdpSocketControlData::default); + +/// UDP socket type, defining whether the socket is used in Client or Server +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UdpSocketType { + /// Socket used for `Client -> Server` + Client, + /// Socket used for `Server -> Client` + Server, +} /// UDP client for communicating with ShadowSocks' server pub struct ProxySocket { + socket_type: UdpSocketType, socket: UdpSocket, method: CipherKind, key: Box<[u8]>, @@ -50,17 +66,28 @@ impl ProxySocket { trace!("connected udp remote {} with {:?}", svr_cfg.addr(), opts); - Ok(ProxySocket::from_socket(context, svr_cfg, socket.into())) + Ok(ProxySocket::from_socket( + UdpSocketType::Client, + context, + svr_cfg, + socket.into(), + )) } /// Create a `ProxySocket` from a `UdpSocket` - pub fn from_socket(context: SharedContext, svr_cfg: &ServerConfig, socket: UdpSocket) -> ProxySocket { + pub fn from_socket( + socket_type: UdpSocketType, + context: SharedContext, + svr_cfg: &ServerConfig, + socket: UdpSocket, + ) -> ProxySocket { let key = svr_cfg.key().to_vec().into_boxed_slice(); let method = svr_cfg.method(); // NOTE: svr_cfg.timeout() is not for this socket, but for associations. ProxySocket { + socket_type, socket, method, key, @@ -91,17 +118,54 @@ impl ProxySocket { .1 } }; - Ok(ProxySocket::from_socket(context, svr_cfg, socket.into())) + Ok(ProxySocket::from_socket( + UdpSocketType::Server, + context, + svr_cfg, + socket.into(), + )) } /// Send a UDP packet to addr through proxy + #[inline] pub async fn send(&self, addr: &Address, payload: &[u8]) -> io::Result { + self.send_with_ctrl(addr, &DEFAULT_SOCKET_CONTROL, payload).await + } + + /// Send a UDP packet to addr through proxy + pub async fn send_with_ctrl( + &self, + addr: &Address, + control: &UdpSocketControlData, + payload: &[u8], + ) -> io::Result { let mut send_buf = BytesMut::new(); - encrypt_payload(&self.context, self.method, &self.key, addr, payload, &mut send_buf); + + match self.socket_type { + UdpSocketType::Client => encrypt_client_payload( + &self.context, + self.method, + &self.key, + addr, + control, + payload, + &mut send_buf, + ), + UdpSocketType::Server => encrypt_server_payload( + &self.context, + self.method, + &self.key, + addr, + control, + payload, + &mut send_buf, + ), + } trace!( - "UDP server client send to {}, payload length {} bytes, packet length {} bytes", + "UDP server client send to {}, control: {:?}, payload length {} bytes, packet length {} bytes", addr, + control, payload.len(), send_buf.len() ); @@ -128,12 +192,45 @@ impl ProxySocket { /// Send a UDP packet to target from proxy pub async fn send_to(&self, target: A, addr: &Address, payload: &[u8]) -> io::Result { + self.send_to_with_ctrl(target, addr, &DEFAULT_SOCKET_CONTROL, payload) + .await + } + + /// Send a UDP packet to target from proxy + pub async fn send_to_with_ctrl( + &self, + target: A, + addr: &Address, + control: &UdpSocketControlData, + payload: &[u8], + ) -> io::Result { let mut send_buf = BytesMut::new(); - encrypt_payload(&self.context, self.method, &self.key, addr, payload, &mut send_buf); + + match self.socket_type { + UdpSocketType::Client => encrypt_client_payload( + &self.context, + self.method, + &self.key, + addr, + control, + payload, + &mut send_buf, + ), + UdpSocketType::Server => encrypt_server_payload( + &self.context, + self.method, + &self.key, + addr, + control, + payload, + &mut send_buf, + ), + } trace!( - "UDP server client send to, addr {}, payload length {} bytes, packet length {} bytes", + "UDP server client send to, addr {}, control: {:?}, payload length {} bytes, packet length {} bytes", addr, + control, payload.len(), send_buf.len() ); @@ -164,6 +261,18 @@ impl ProxySocket { /// /// It is recommended to allocate a buffer to have at least 65536 bytes. pub async fn recv(&self, recv_buf: &mut [u8]) -> io::Result<(usize, Address, usize)> { + self.recv_with_ctrl(recv_buf).await.map(|(n, a, rn, _)| (n, a, rn)) + } + + /// Receive packet from Shadowsocks' UDP server + /// + /// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet + /// + /// It is recommended to allocate a buffer to have at least 65536 bytes. + pub async fn recv_with_ctrl( + &self, + recv_buf: &mut [u8], + ) -> io::Result<(usize, Address, usize, Option)> { // Waiting for response from server SERVER -> CLIENT let recv_n = match self.recv_timeout { None => self.socket.recv(recv_buf).await?, @@ -174,16 +283,24 @@ impl ProxySocket { }, }; - let (n, addr) = decrypt_payload(&self.context, self.method, &self.key, &mut recv_buf[..recv_n]).await?; + let (n, addr, control) = match self.socket_type { + UdpSocketType::Client => { + decrypt_server_payload(&self.context, self.method, &self.key, &mut recv_buf[..recv_n]).await? + } + UdpSocketType::Server => { + decrypt_client_payload(&self.context, self.method, &self.key, &mut recv_buf[..recv_n]).await? + } + }; trace!( - "UDP server client receive from {}, packet length {} bytes, payload length {} bytes", + "UDP server client receive from {}, control: {:?}, packet length {} bytes, payload length {} bytes", addr, + control, recv_n, n ); - Ok((n, addr, recv_n)) + Ok((n, addr, recv_n, control)) } /// Receive packet from Shadowsocks' UDP server @@ -192,6 +309,20 @@ impl ProxySocket { /// /// It is recommended to allocate a buffer to have at least 65536 bytes. pub async fn recv_from(&self, recv_buf: &mut [u8]) -> io::Result<(usize, SocketAddr, Address, usize)> { + self.recv_from_with_ctrl(recv_buf) + .await + .map(|(n, sa, a, rn, _)| (n, sa, a, rn)) + } + + /// Receive packet from Shadowsocks' UDP server + /// + /// This function will use `recv_buf` to store intermediate data, so it has to be big enough to store the whole shadowsocks' packet + /// + /// It is recommended to allocate a buffer to have at least 65536 bytes. + pub async fn recv_from_with_ctrl( + &self, + recv_buf: &mut [u8], + ) -> io::Result<(usize, SocketAddr, Address, usize, Option)> { // Waiting for response from server SERVER -> CLIENT let (recv_n, target_addr) = match self.recv_timeout { None => self.socket.recv_from(recv_buf).await?, @@ -201,17 +332,26 @@ impl ProxySocket { Err(..) => return Err(io::ErrorKind::TimedOut.into()), }, }; - let (n, addr) = decrypt_payload(&self.context, self.method, &self.key, &mut recv_buf[..recv_n]).await?; + + let (n, addr, control) = match self.socket_type { + UdpSocketType::Client => { + decrypt_server_payload(&self.context, self.method, &self.key, &mut recv_buf[..recv_n]).await? + } + UdpSocketType::Server => { + decrypt_client_payload(&self.context, self.method, &self.key, &mut recv_buf[..recv_n]).await? + } + }; trace!( - "UDP server client receive from {}, addr {}, packet length {} bytes, payload length {} bytes", + "UDP server client receive from {}, addr {}, control: {:?}, packet length {} bytes, payload length {} bytes", target_addr, addr, + control, recv_n, n, ); - Ok((n, target_addr, addr, recv_n)) + Ok((n, target_addr, addr, recv_n, control)) } /// Get local addr of socket diff --git a/crates/shadowsocks/src/relay/udprelay/stream.rs b/crates/shadowsocks/src/relay/udprelay/stream.rs new file mode 100644 index 000000000000..34c741196fce --- /dev/null +++ b/crates/shadowsocks/src/relay/udprelay/stream.rs @@ -0,0 +1,100 @@ +//! Shadowsocks UDP Stream Protocol +//! +//! Payload with stream cipher +//! ```plain +//! +-------+----------+ +//! | IV | Payload | +//! +-------+----------+ +//! | Fixed | Variable | +//! +-------+----------+ +//! ``` + +use std::io::{self, Cursor, ErrorKind}; + +use byte_string::ByteStr; +use bytes::{BufMut, BytesMut}; +use log::trace; + +use crate::{ + context::Context, + crypto::{v1::Cipher, CipherKind}, + relay::socks5::Address, +}; + +/// Encrypt UDP stream protocol packet +pub fn encrypt_payload_stream( + context: &Context, + method: CipherKind, + key: &[u8], + addr: &Address, + payload: &[u8], + dst: &mut BytesMut, +) { + let iv_len = method.iv_len(); + let addr_len = addr.serialized_len(); + + // Packet = IV + ADDRESS + PAYLOAD + dst.reserve(iv_len + addr_len + payload.len()); + + // Generate IV + dst.resize(iv_len, 0); + let iv = &mut dst[..iv_len]; + + if iv_len > 0 { + context.generate_nonce(iv, false); + trace!("UDP packet generated stream iv {:?}", ByteStr::new(iv)); + } + + let mut cipher = Cipher::new(method, key, iv); + + addr.write_to_buf(dst); + dst.put_slice(payload); + let m = &mut dst[iv_len..]; + cipher.encrypt_packet(m); +} + +/// Decrypt UDP stream protocol packet +pub async fn decrypt_payload_stream( + _context: &Context, + method: CipherKind, + key: &[u8], + payload: &mut [u8], +) -> io::Result<(usize, Address)> { + let plen = payload.len(); + let iv_len = method.iv_len(); + + if plen < iv_len { + let err = io::Error::new(ErrorKind::InvalidData, "udp packet too short for iv"); + return Err(err); + } + + let (iv, data) = payload.split_at_mut(iv_len); + // context.check_nonce_replay(iv)?; + + trace!("UDP packet got stream IV {:?}", ByteStr::new(iv)); + let mut cipher = Cipher::new(method, key, iv); + + assert!(cipher.decrypt_packet(data)); + + let (dn, addr) = parse_packet(data).await?; + + let data_start_idx = iv_len + dn; + let data_length = payload.len() - data_start_idx; + payload.copy_within(data_start_idx.., 0); + + Ok((data_length, addr)) +} + +async fn parse_packet(buf: &[u8]) -> io::Result<(usize, Address)> { + let mut cur = Cursor::new(buf); + match Address::read_from(&mut cur).await { + Ok(address) => { + let pos = cur.position() as usize; + Ok((pos, address)) + } + Err(..) => { + let err = io::Error::new(ErrorKind::InvalidData, "parse udp packet Address failed"); + Err(err) + } + } +} diff --git a/crates/shadowsocks/tests/tcp.rs b/crates/shadowsocks/tests/tcp.rs index 5fe100ead908..ac44a4b98269 100644 --- a/crates/shadowsocks/tests/tcp.rs +++ b/crates/shadowsocks/tests/tcp.rs @@ -16,7 +16,7 @@ use tokio::{ use shadowsocks::{ config::{ServerConfig, ServerType}, context::Context, - crypto::v1::CipherKind, + crypto::CipherKind, relay::{ socks5::Address, tcprelay::{ @@ -44,7 +44,7 @@ async fn handle_tcp_tunnel_server_client( remote }; - let (mut sr, mut sw) = stream.into_split(); + let (mut sr, mut sw) = tokio::io::split(stream); let (mut mr, mut mw) = remote.split(); let l2r = copy_from_encrypted(method, &mut sr, &mut mw); @@ -70,7 +70,7 @@ async fn handle_tcp_tunnel_local_client( let remote = ProxyClientStream::connect(context, &svr_cfg, target_addr).await?; let (mut lr, mut lw) = stream.split(); - let (mut sr, mut sw) = remote.into_split(); + let (mut sr, mut sw) = tokio::io::split(remote); let l2s = copy_to_encrypted(svr_cfg.method(), &mut lr, &mut sw); let s2l = copy_from_encrypted(svr_cfg.method(), &mut sr, &mut lw); diff --git a/crates/shadowsocks/tests/tcp_tfo.rs b/crates/shadowsocks/tests/tcp_tfo.rs index 058a01cd51ea..7dc6f954666b 100644 --- a/crates/shadowsocks/tests/tcp_tfo.rs +++ b/crates/shadowsocks/tests/tcp_tfo.rs @@ -15,7 +15,7 @@ use log::debug; use shadowsocks::{ config::ServerType, context::Context, - crypto::v1::CipherKind, + crypto::CipherKind, net::{AcceptOpts, ConnectOpts}, relay::{ socks5::Address, @@ -57,7 +57,7 @@ async fn tcp_tunnel_tfo() { Address::DomainNameAddress(name, port) => TcpStream::connect((name.as_str(), port)).await.unwrap(), }; - let (mut lr, mut lw) = stream.into_split(); + let (mut lr, mut lw) = tokio::io::split(stream); let (mut rr, mut rw) = remote.into_split(); let l2r = copy_from_encrypted(CipherKind::NONE, &mut lr, &mut rw); diff --git a/crates/shadowsocks/tests/udp.rs b/crates/shadowsocks/tests/udp.rs index c548ecd61e93..14030dfc9069 100644 --- a/crates/shadowsocks/tests/udp.rs +++ b/crates/shadowsocks/tests/udp.rs @@ -7,7 +7,7 @@ use tokio::{net::UdpSocket, sync::Barrier}; use shadowsocks::{ config::{ServerConfig, ServerType}, context::{Context, SharedContext}, - crypto::v1::CipherKind, + crypto::CipherKind, relay::{socks5::Address, udprelay::ProxySocket}, }; diff --git a/src/service/local.rs b/src/service/local.rs index 58894b4bfad3..8395422d7acd 100644 --- a/src/service/local.rs +++ b/src/service/local.rs @@ -18,7 +18,7 @@ use shadowsocks_service::{ local::loadbalancing::PingBalancer, shadowsocks::{ config::{Mode, ServerAddr, ServerConfig}, - crypto::v1::{available_ciphers, CipherKind}, + crypto::{available_ciphers, CipherKind}, plugin::PluginConfig, }, }; diff --git a/src/service/manager.rs b/src/service/manager.rs index 1134b29a1d1d..52a306b4fa90 100644 --- a/src/service/manager.rs +++ b/src/service/manager.rs @@ -15,7 +15,7 @@ use shadowsocks_service::{ run_manager, shadowsocks::{ config::{ManagerAddr, Mode}, - crypto::v1::{available_ciphers, CipherKind}, + crypto::{available_ciphers, CipherKind}, plugin::PluginConfig, }, }; diff --git a/src/service/server.rs b/src/service/server.rs index 3a4d57bb14d8..0ff42e673f44 100644 --- a/src/service/server.rs +++ b/src/service/server.rs @@ -13,7 +13,7 @@ use shadowsocks_service::{ run_server, shadowsocks::{ config::{ManagerAddr, Mode, ServerAddr, ServerConfig}, - crypto::v1::{available_ciphers, CipherKind}, + crypto::{available_ciphers, CipherKind}, plugin::PluginConfig, }, }; diff --git a/tests/socks4.rs b/tests/socks4.rs index 246b67502263..326a08ff178c 100644 --- a/tests/socks4.rs +++ b/tests/socks4.rs @@ -17,7 +17,7 @@ use shadowsocks_service::{ run_server, shadowsocks::{ config::{ServerAddr, ServerConfig}, - crypto::v1::CipherKind, + crypto::CipherKind, }, }; diff --git a/tests/socks5.rs b/tests/socks5.rs index 1872e6eb1239..f6357b733c87 100644 --- a/tests/socks5.rs +++ b/tests/socks5.rs @@ -17,7 +17,7 @@ use shadowsocks_service::{ run_server, shadowsocks::{ config::{Mode, ServerAddr, ServerConfig}, - crypto::v1::CipherKind, + crypto::CipherKind, relay::socks5::Address, }, }; diff --git a/tests/udp.rs b/tests/udp.rs index e6d526e403c6..a12515d981a5 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -11,7 +11,7 @@ use shadowsocks_service::{ local::socks::client::socks5::Socks5UdpClient, run_local, run_server, - shadowsocks::{config::Mode, crypto::v1::CipherKind, relay::socks5::Address, ServerConfig}, + shadowsocks::{config::Mode, crypto::CipherKind, relay::socks5::Address, ServerConfig}, }; const SERVER_ADDR: &str = "127.0.0.1:8093";