AEAD-2022 ciphers (new protocol) (#811)

This commit is contained in:
ty
2022-04-19 11:48:43 +08:00
committed by GitHub
parent c99ed3aa7a
commit 7c154d2340
49 changed files with 3423 additions and 656 deletions
+4 -4
View File
@@ -47,19 +47,19 @@ jobs:
run: cargo test --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --no-default-features --no-fail-fast
- name: Build with All Features Enabled (Unix)
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
run: cargo build --verbose --features "local-http-rustls local-redir local-dns dns-over-tls dns-over-https stream-cipher local-tun"
run: cargo build --verbose --features "local-http-rustls local-redir local-dns local-tun dns-over-tls dns-over-https stream-cipher aead-cipher-2022"
- name: Build with All Features Enabled (Windows)
if: ${{ runner.os == 'Windows' }}
run: cargo build --verbose --features "local-http-rustls local-dns dns-over-tls dns-over-https stream-cipher"
run: cargo build --verbose --features "local-http-rustls local-dns dns-over-tls dns-over-https stream-cipher aead-cipher-2022"
- name: Build with All Features Enabled - shadowsocks
run: cargo build --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher"
run: cargo build --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher aead-cipher-2022"
- name: Clippy Check
uses: actions-rs/clippy-check@v1
with:
name: clippy-${{ matrix.platform }}
token: ${{ secrets.GITHUB_TOKEN }}
args: |
--verbose --features "local-http-rustls local-redir local-dns dns-over-tls dns-over-https stream-cipher" -- -Z macro-backtrace
--features "local-http-rustls local-redir local-dns dns-over-tls dns-over-https stream-cipher aead-cipher-2022" -- -Z macro-backtrace
-W clippy::absurd_extreme_comparisons
-W clippy::erasing_op
-A clippy::collapsible_else_if
Generated
+128 -57
View File
@@ -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",
]
@@ -300,21 +337,40 @@ dependencies = [
]
[[package]]
name = "clap"
version = "3.1.8"
name = "cipher"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "3.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aad2534fad53df1cc12519c5cda696dd3e20e6118a027e24054aea14a0bdcbe"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"os_str_bytes",
"strsim",
"termcolor",
"terminal_size",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "cmake"
version = "0.1.48"
@@ -330,6 +386,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 +465,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
"cipher 0.3.0",
]
[[package]]
@@ -590,9 +652,9 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c"
dependencies = [
"cfg-if",
"libc",
@@ -868,9 +930,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
checksum = "6330e8a36bd8c859f3fa6d9382911fbb7147ec39807f63b923933a247240b9ba"
[[package]]
name = "httpdate"
@@ -949,6 +1011,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"
@@ -978,9 +1049,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
[[package]]
name = "iprange"
@@ -1040,9 +1111,9 @@ dependencies = [
[[package]]
name = "kqueue"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058a107a784f8be94c7d35c1300f4facced2e93d2fbe5b1452b44e905ddca4a9"
checksum = "97caf428b83f7c86809b7450722cd1f2b1fc7fb23aa7b9dee7e72ed14d048352"
dependencies = [
"kqueue-sys",
"libc",
@@ -1066,9 +1137,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.122"
version = "0.2.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259"
checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
[[package]]
name = "libmimalloc-sys"
@@ -1395,9 +1466,6 @@ name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "p256"
@@ -1611,9 +1679,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
@@ -1765,9 +1833,9 @@ dependencies = [
[[package]]
name = "rpmalloc-sys"
version = "0.2.2+1.4.1"
version = "0.2.3+b097fd0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370e623bf2ca97dd497b7dd0e2889ec953a46c8c268489a818a5e305633e8609"
checksum = "8d4b7d5e225a53887ee57fcec492eaf114b8e290f7072d035adc6ddd6810b67b"
dependencies = [
"cc",
"libc",
@@ -1788,25 +1856,16 @@ dependencies = [
[[package]]
name = "rustls-native-certs"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943"
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
dependencies = [
"openssl-probe",
"rustls-pemfile 0.2.1",
"rustls-pemfile 1.0.0",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9"
dependencies = [
"base64",
]
[[package]]
name = "rustls-pemfile"
version = "0.3.0"
@@ -1816,6 +1875,15 @@ dependencies = [
"base64",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
dependencies = [
"base64",
]
[[package]]
name = "ryu"
version = "1.0.9"
@@ -2002,8 +2070,9 @@ dependencies = [
[[package]]
name = "shadowsocks"
version = "1.14.3"
version = "1.15.0"
dependencies = [
"aes 0.8.1",
"arc-swap 1.5.0",
"async-trait",
"base64",
@@ -2015,6 +2084,7 @@ dependencies = [
"futures",
"libc",
"log",
"lru_time_cache",
"nix",
"notify",
"once_cell",
@@ -2026,7 +2096,7 @@ dependencies = [
"serde_urlencoded",
"shadowsocks-crypto",
"socket2",
"spin 0.9.2",
"spin 0.9.3",
"thiserror",
"tokio",
"tokio-tfo",
@@ -2037,13 +2107,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#d0d9914fbf01ac85cf1120cbc7888de6bd0e5817"
dependencies = [
"aes",
"aes 0.7.5",
"aes-gcm",
"aes-gcm-siv",
"blake3",
"bytes",
"ccm",
"cfg-if",
"chacha20",
@@ -2057,7 +2128,7 @@ dependencies = [
[[package]]
name = "shadowsocks-rust"
version = "1.14.4"
version = "1.15.0"
dependencies = [
"build-time",
"byte_string",
@@ -2090,7 +2161,7 @@ dependencies = [
[[package]]
name = "shadowsocks-service"
version = "1.14.3"
version = "1.15.0"
dependencies = [
"arc-swap 1.5.0",
"async-trait",
@@ -2120,7 +2191,7 @@ dependencies = [
"shadowsocks",
"smoltcp",
"socket2",
"spin 0.9.2",
"spin 0.9.3",
"thiserror",
"tokio",
"tokio-native-tls",
@@ -2230,9 +2301,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5"
checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d"
dependencies = [
"lock_api",
]
@@ -2495,9 +2566,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.32"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
dependencies = [
"cfg-if",
"log",
@@ -2519,9 +2590,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.24"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee"
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
dependencies = [
"lazy_static",
]
+5 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-rust"
version = "1.14.4"
version = "1.15.0"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
@@ -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.
@@ -165,7 +168,7 @@ jemallocator = { version = "0.3", optional = true }
snmalloc-rs = { version = "0.2", optional = true }
rpmalloc = { version = "0.2", optional = true }
shadowsocks-service = { version = "1.14.1", path = "./crates/shadowsocks-service" }
shadowsocks-service = { version = "1.15.0", path = "./crates/shadowsocks-service" }
[target.'cfg(unix)'.dependencies]
daemonize = "0.4"
+2
View File
@@ -55,6 +55,8 @@ Related Projects:
- `aead-cipher-extra` - Enable non-standard AEAD ciphers
- `aead-cipher-2022` - Enable AEAD-2022 ciphers ([Draft](https://github.com/shadowsocks/shadowsocks-org/issues/194#issuecomment-1065833908))
#### Memory Allocators
This project uses system (libc) memory allocator (Rust's default). But it also allows you to use other famous allocators by features:
+1 -1
View File
@@ -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::{
+8 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-service"
version = "1.14.3"
version = "1.15.0"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
@@ -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 = { version = "0.8", features = ["small_rng"] }
futures = "0.3"
tokio = { version = "1.5", features = ["io-util", "macros", "net", "parking_lot", "rt", "sync", "time"] }
@@ -123,7 +126,7 @@ smoltcp = { version = "0.8", optional = true, default-features = false, features
serde = { version = "1.0", features = ["derive"] }
json5 = "0.4"
shadowsocks = { version = "1.14.1", path = "../shadowsocks", default-features = false }
shadowsocks = { version = "1.15.0", path = "../shadowsocks", default-features = false }
# Just for the ioctl call macro
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
+1 -1
View File
@@ -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")]
@@ -31,9 +31,9 @@ impl Connector {
}
impl Service<Uri> 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<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
@@ -67,7 +67,7 @@ impl Service<Uri> for Connector {
}
}
}
.boxed(),
.boxed(),
}
}
}
@@ -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<ProxyClientStream<MonProxyStream<TcpStream>>> 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<MonProxyStream<TcpStream>>),
Bypassed(#[pin] ReadHalf<TcpStream>),
}
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<io::Result<()>> {
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<MonProxyStream<TcpStream>>),
Bypassed(#[pin] WriteHalf<TcpStream>),
}
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<io::Result<usize>> {
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<io::Result<()>> {
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<io::Result<()>> {
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<io::Result<usize>> {
match self.project() {
AutoProxyClientStreamWriteHalfProj::Proxied(s) => s.poll_write_vectored(cx, bufs),
AutoProxyClientStreamWriteHalfProj::Bypassed(s) => s.poll_write_vectored(cx, bufs),
}
}
}
@@ -1,6 +1,7 @@
//! UDP Association Managing
use std::{
cell::RefCell,
io::{self, ErrorKind},
marker::PhantomData,
net::SocketAddr,
@@ -13,20 +14,26 @@ 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,
},
};
use crate::{
local::{context::ServiceContext, loadbalancing::PingBalancer},
net::{MonProxySocket, UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE, UDP_ASSOCIATION_SEND_CHANNEL_SIZE},
net::{
packet_window::PacketWindowFilter,
MonProxySocket,
UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE,
UDP_ASSOCIATION_SEND_CHANNEL_SIZE,
},
};
/// Writer for sending packets back to client
@@ -170,6 +177,24 @@ where
}
}
#[derive(Debug, Clone)]
struct ServerContext {
packet_window_filter: PacketWindowFilter,
}
#[derive(Clone)]
struct ServerSessionContext {
server_session_map: LruCache<u64, ServerContext>,
}
impl ServerSessionContext {
fn new() -> ServerSessionContext {
ServerSessionContext {
server_session_map: LruCache::with_expiry_duration_and_capacity(Duration::from_secs(30 * 60), 5),
}
}
}
struct UdpAssociationContext<W>
where
W: UdpInboundWrite + Send + Sync + Unpin + 'static,
@@ -183,6 +208,9 @@ where
keepalive_flag: bool,
balancer: PingBalancer,
respond_writer: W,
client_session_id: u64,
client_packet_id: u64,
server_session: Option<ServerSessionContext>,
}
impl<W> Drop for UdpAssociationContext<W>
@@ -194,6 +222,15 @@ where
}
}
thread_local! {
static CLIENT_SESSION_RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
}
#[inline]
fn generate_client_session_id() -> u64 {
CLIENT_SESSION_RNG.with(|rng| rng.borrow_mut().gen())
}
impl<W> UdpAssociationContext<W>
where
W: UdpInboundWrite + Send + Sync + Unpin + 'static,
@@ -220,6 +257,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 +319,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 +329,33 @@ where
}
};
if let Some(control) = control_opt {
// Check if Packet ID is in the window
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(|| {
trace!(
"udp server with session {} for {} created",
control.client_session_id,
self.peer_addr,
);
ServerContext {
packet_window_filter: PacketWindowFilter::new()
}
});
if !session_context.packet_window_filter.validate_packet_id(packet_id, u64::MAX) {
error!("udp {} packet_id {} out of window", self.peer_addr, packet_id);
continue;
}
}
self.send_received_respond_packet(&addr, &proxied_buffer[..n], false).await;
}
@@ -322,14 +391,14 @@ where
async fn receive_from_proxied_opt(
socket: &Option<MonProxySocket>,
buf: &mut Vec<u8>,
) -> io::Result<(usize, Address)> {
) -> io::Result<(usize, Address, Option<UdpSocketControlData>)> {
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 +503,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!(
@@ -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,
};
@@ -24,7 +26,12 @@ use tokio::{net::UdpSocket, sync::mpsc, task::JoinHandle, time};
use crate::{
local::{context::ServiceContext, loadbalancing::PingBalancer},
net::{MonProxySocket, UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE, UDP_ASSOCIATION_SEND_CHANNEL_SIZE},
net::{
packet_window::PacketWindowFilter,
MonProxySocket,
UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE,
UDP_ASSOCIATION_SEND_CHANNEL_SIZE,
},
};
type AssociationMap = LruCache<SocketAddr, UdpAssociation>;
@@ -197,6 +204,24 @@ impl UdpAssociation {
}
}
#[derive(Debug, Clone)]
struct ServerContext {
packet_window_filter: PacketWindowFilter,
}
#[derive(Clone)]
struct ServerSessionContext {
server_session_map: LruCache<u64, ServerContext>,
}
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<ServiceContext>,
peer_addr: SocketAddr,
@@ -206,6 +231,9 @@ struct UdpAssociationContext {
keepalive_flag: bool,
balancer: PingBalancer,
inbound: Arc<UdpSocket>,
client_session_id: u64,
client_packet_id: u64,
server_session: Option<ServerSessionContext>,
}
impl Drop for UdpAssociationContext {
@@ -214,6 +242,15 @@ impl Drop for UdpAssociationContext {
}
}
thread_local! {
static CLIENT_SESSION_RNG: RefCell<SmallRng> = 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<ServiceContext>,
@@ -237,6 +274,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 +304,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 +314,33 @@ impl UdpAssociationContext {
}
};
if let Some(control) = control_opt {
// Check if Packet ID is in the window
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(|| {
trace!(
"udp server with session {} for {} created",
control.client_session_id,
self.peer_addr,
);
ServerContext {
packet_window_filter: PacketWindowFilter::new()
}
});
if !session_context.packet_window_filter.validate_packet_id(packet_id, u64::MAX) {
error!("udp {} packet_id {} out of window", self.peer_addr, packet_id);
continue;
}
}
self.send_received_respond_packet(&addr, &proxied_buffer[..n]).await;
}
@@ -291,14 +360,14 @@ impl UdpAssociationContext {
async fn receive_from_proxied_opt(
socket: &Option<MonProxySocket>,
buf: &mut Vec<u8>,
) -> io::Result<(usize, Address)> {
) -> io::Result<(usize, Address, Option<UdpSocketControlData>)> {
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 +410,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!(
@@ -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,
@@ -5,6 +5,7 @@ pub use self::{flow::FlowStat, mon_socket::MonProxySocket, mon_stream::MonProxyS
pub mod flow;
pub mod mon_socket;
pub mod mon_stream;
pub mod packet_window;
pub mod utils;
/// Packet size for all UDP associations' send queue
@@ -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<A: ToSocketAddrs>(&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<A: ToSocketAddrs>(
&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<UdpSocketControlData>)> {
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<UdpSocketControlData>)> {
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
}
}
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: MIT
//
// Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
//! Packet window
//!
//! https://github.com/WireGuard/wireguard-go/blob/master/replay/replay.go
const BLOCK_BIT_LOG: u64 = 6; // 1<<6 == 64 bits
const BLOCK_BITS: u64 = 1 << BLOCK_BIT_LOG; // must be power of 2
const RING_BLOCKS: u64 = 1 << 7; // must be power of 2
const WINDOW_SIZE: u64 = (RING_BLOCKS - 1) * BLOCK_BITS;
const BLOCK_MASK: u64 = RING_BLOCKS - 1;
const BIT_MASK: u64 = BLOCK_BITS - 1;
/// Packet window for checking `packet_id` is in the sliding window
#[derive(Debug, Clone)]
pub struct PacketWindowFilter {
last_packet_id: u64,
packet_ring: [u64; RING_BLOCKS as usize],
}
impl Default for PacketWindowFilter {
fn default() -> PacketWindowFilter {
PacketWindowFilter::new()
}
}
impl PacketWindowFilter {
/// Create an empty filter
pub fn new() -> PacketWindowFilter {
PacketWindowFilter {
last_packet_id: 0,
packet_ring: [0u64; RING_BLOCKS as usize],
}
}
/// Reset filter to the initial state
pub fn reset(&mut self) {
self.last_packet_id = 0;
self.packet_ring[0] = 0;
}
/// Check and remember the `packet_id`
///
/// Overlimit `packet_id >= limit` are always rejected
pub fn validate_packet_id(&mut self, packet_id: u64, limit: u64) -> bool {
if packet_id >= limit {
return false;
}
let mut index_block = packet_id >> BLOCK_BIT_LOG;
if packet_id > self.last_packet_id {
// Move the window forward
let current = self.last_packet_id >> BLOCK_BIT_LOG;
let mut diff = index_block - current;
if diff > RING_BLOCKS {
// Clear the whole filter
diff = RING_BLOCKS;
}
for d in 1..=diff {
let i = current + d;
self.packet_ring[(i & BLOCK_MASK) as usize] = 0;
}
self.last_packet_id = packet_id;
} else if self.last_packet_id - packet_id > WINDOW_SIZE {
// Behind the current window
return false;
}
// Check and set bit
index_block &= BLOCK_MASK;
let index_bit = packet_id & BIT_MASK;
let old = self.packet_ring[index_block as usize];
let new = old | (1 << index_bit);
self.packet_ring[index_block as usize] = new;
old != new
}
}
#[cfg(test)]
mod test {
use super::*;
use std::cell::RefCell;
#[test]
fn test_packet_window() {
const REJECT_AFTER_MESSAGES: u64 = u64::MAX - (1u64 << 13);
let filter = RefCell::new(PacketWindowFilter::new());
let test_number = RefCell::new(0);
#[allow(non_snake_case)]
let T = |n: u64, expected: bool| {
*(test_number.borrow_mut()) += 1;
if filter.borrow_mut().validate_packet_id(n, REJECT_AFTER_MESSAGES) != expected {
panic!("Test {} failed, {} {}", test_number.borrow(), n, expected);
}
};
const T_LIM: u64 = WINDOW_SIZE + 1;
T(0, true); // 1
T(1, true); // 2
T(1, false); // 3
T(9, true); // 4
T(8, true); // 5
T(7, true); // 6
T(7, false); // 7
T(T_LIM, true); // 8
T(T_LIM - 1, true); // 9
T(T_LIM - 1, false); // 10
T(T_LIM - 2, true); // 11
T(2, true); // 12
T(2, false); // 13
T(T_LIM + 16, true); // 14
T(3, false); // 15
T(T_LIM + 16, false); // 16
T(T_LIM * 4, true); // 17
T(T_LIM * 4 - (T_LIM - 1), true); // 18
T(10, false); // 19
T(T_LIM * 4 - T_LIM, false); // 20
T(T_LIM * 4 - (T_LIM + 1), false); // 21
T(T_LIM * 4 - (T_LIM - 2), true); // 22
T(T_LIM * 4 + 1 - T_LIM, false); // 23
T(0, false); // 24
T(REJECT_AFTER_MESSAGES, false); // 25
T(REJECT_AFTER_MESSAGES - 1, true); // 26
T(REJECT_AFTER_MESSAGES, false); // 27
T(REJECT_AFTER_MESSAGES - 1, false); // 28
T(REJECT_AFTER_MESSAGES - 2, true); // 29
T(REJECT_AFTER_MESSAGES + 1, false); // 30
T(REJECT_AFTER_MESSAGES + 2, false); // 31
T(REJECT_AFTER_MESSAGES - 2, false); // 32
T(REJECT_AFTER_MESSAGES - 3, true); // 33
T(0, false); // 34
println!("Bulk test 1");
filter.borrow_mut().reset();
*(test_number.borrow_mut()) = 0;
for i in 1..=WINDOW_SIZE {
T(i, true);
}
T(0, true);
T(0, false);
println!("Bulk test 2");
filter.borrow_mut().reset();
*(test_number.borrow_mut()) = 0;
for i in 2..=WINDOW_SIZE + 1 {
T(i, true);
}
T(1, true);
T(0, false);
println!("Bulk test 3");
filter.borrow_mut().reset();
*(test_number.borrow_mut()) = 0;
for i in (1..=WINDOW_SIZE + 1).rev() {
T(i, true);
}
println!("Bulk test 4");
filter.borrow_mut().reset();
*(test_number.borrow_mut()) = 0;
for i in (2..=WINDOW_SIZE + 2).rev() {
T(i, true);
}
T(0, false);
println!("Bulk test 5");
filter.borrow_mut().reset();
*(test_number.borrow_mut()) = 0;
for i in (1..=WINDOW_SIZE).rev() {
T(i, true);
}
T(WINDOW_SIZE + 1, true);
T(0, false);
println!("Bulk test 6");
filter.borrow_mut().reset();
*(test_number.borrow_mut()) = 0;
for i in (1..=WINDOW_SIZE).rev() {
T(i, true);
}
T(0, true);
T(WINDOW_SIZE + 1, true);
}
}
@@ -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(),
@@ -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
+313 -54
View File
@@ -1,6 +1,7 @@
//! Shadowsocks UDP server
use std::{
cell::RefCell,
io::{self, ErrorKind},
net::SocketAddr,
sync::Arc,
@@ -11,28 +12,78 @@ 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::{
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,
};
use tokio::{sync::mpsc, task::JoinHandle, time};
use crate::net::{MonProxySocket, UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE, UDP_ASSOCIATION_SEND_CHANNEL_SIZE};
use crate::net::{
packet_window::PacketWindowFilter,
MonProxySocket,
UDP_ASSOCIATION_KEEP_ALIVE_CHANNEL_SIZE,
UDP_ASSOCIATION_SEND_CHANNEL_SIZE,
};
use super::context::ServiceContext;
#[derive(Debug, Clone, Copy)]
enum NatKey {
PeerAddr(SocketAddr),
#[cfg(feature = "aead-cipher-2022")]
SessionId(u64),
}
type AssociationMap = LruCache<SocketAddr, UdpAssociation>;
#[cfg(feature = "aead-cipher-2022")]
type SessionMap = LruCache<u64, UdpAssociation>;
enum NatMap {
Association(AssociationMap),
#[cfg(feature = "aead-cipher-2022")]
Session(SessionMap),
}
impl NatMap {
fn cleanup_expired(&mut self) {
match *self {
NatMap::Association(ref mut m) => {
m.iter();
}
#[cfg(feature = "aead-cipher-2022")]
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);
}
#[cfg(feature = "aead-cipher-2022")]
(NatMap::Session(ref mut m), NatKey::SessionId(ref session_id)) => {
m.get(session_id);
}
#[allow(unreachable_patterns)]
_ => unreachable!("NatMap & NatKey mismatch"),
}
}
}
pub struct UdpServer {
context: Arc<ServiceContext>,
assoc_map: AssociationMap,
keepalive_tx: mpsc::Sender<SocketAddr>,
keepalive_rx: mpsc::Receiver<SocketAddr>,
assoc_map: NatMap,
keepalive_tx: mpsc::Sender<NatKey>,
keepalive_rx: mpsc::Receiver<NatKey>,
time_to_live: Duration,
accept_opts: AcceptOpts,
}
@@ -40,14 +91,31 @@ pub struct UdpServer {
impl UdpServer {
pub fn new(
context: Arc<ServiceContext>,
method: CipherKind,
time_to_live: Option<Duration>,
capacity: Option<usize>,
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<K, V>(time_to_live: Duration, capacity: Option<usize>) -> LruCache<K, V>
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 +148,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 +190,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,31 +208,70 @@ impl UdpServer {
listener: &Arc<MonProxySocket>,
peer_addr: SocketAddr,
target_addr: Address,
control: Option<UdpSocketControlData>,
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_association(
self.context.clone(),
listener.clone(),
peer_addr,
self.keepalive_tx.clone(),
);
debug!("created udp association for {}", peer_addr);
assoc.try_send((peer_addr, target_addr, Bytes::copy_from_slice(data), control))?;
m.insert(peer_addr, assoc);
}
#[cfg(feature = "aead-cipher-2022")]
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);
}
}
let assoc = UdpAssociation::new(
self.context.clone(),
listener.clone(),
peer_addr,
self.keepalive_tx.clone(),
);
debug!("created udp association for {}", peer_addr);
assoc.try_send((target_addr, Bytes::copy_from_slice(data)))?;
self.assoc_map.insert(peer_addr, assoc);
Ok(())
}
}
type UdpAssociationSendMessage = (SocketAddr, Address, Bytes, Option<UdpSocketControlData>);
struct UdpAssociation {
assoc_handle: JoinHandle<()>,
sender: mpsc::Sender<(Address, Bytes)>,
sender: mpsc::Sender<UdpAssociationSendMessage>,
}
impl Drop for UdpAssociation {
@@ -174,17 +281,30 @@ impl Drop for UdpAssociation {
}
impl UdpAssociation {
fn new(
fn new_association(
context: Arc<ServiceContext>,
inbound: Arc<MonProxySocket>,
peer_addr: SocketAddr,
keepalive_tx: mpsc::Sender<SocketAddr>,
keepalive_tx: mpsc::Sender<NatKey>,
) -> 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, None);
UdpAssociation { assoc_handle, sender }
}
fn try_send(&self, data: (Address, Bytes)) -> io::Result<()> {
#[cfg(feature = "aead-cipher-2022")]
fn new_session(
context: Arc<ServiceContext>,
inbound: Arc<MonProxySocket>,
peer_addr: SocketAddr,
keepalive_tx: mpsc::Sender<NatKey>,
client_session_id: u64,
) -> UdpAssociation {
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: UdpAssociationSendMessage) -> 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 +313,36 @@ impl UdpAssociation {
}
}
struct ClientContext {
packet_window_filter: PacketWindowFilter,
}
struct ClientSessionContext {
client_session_id: u64,
client_context_map: LruCache<SocketAddr, ClientContext>,
}
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<ServiceContext>,
peer_addr: SocketAddr,
outbound_ipv4_socket: Option<OutboundUdpSocket>,
outbound_ipv6_socket: Option<OutboundUdpSocket>,
keepalive_tx: mpsc::Sender<SocketAddr>,
keepalive_tx: mpsc::Sender<NatKey>,
keepalive_flag: bool,
inbound: Arc<MonProxySocket>,
// AEAD 2022
client_session: Option<ClientSessionContext>,
server_session_id: u64,
server_packet_id: u64,
}
impl Drop for UdpAssociationContext {
@@ -209,13 +351,23 @@ impl Drop for UdpAssociationContext {
}
}
thread_local! {
static CLIENT_SESSION_RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
}
#[inline]
fn generate_server_session_id() -> u64 {
CLIENT_SESSION_RNG.with(|rng| rng.borrow_mut().gen())
}
impl UdpAssociationContext {
fn create(
context: Arc<ServiceContext>,
inbound: Arc<MonProxySocket>,
peer_addr: SocketAddr,
keepalive_tx: mpsc::Sender<SocketAddr>,
) -> (JoinHandle<()>, mpsc::Sender<(Address, Bytes)>) {
keepalive_tx: mpsc::Sender<NatKey>,
client_session_id: Option<u64>,
) -> (JoinHandle<()>, mpsc::Sender<UdpAssociationSendMessage>) {
// 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.
@@ -229,13 +381,17 @@ impl UdpAssociationContext {
keepalive_tx,
keepalive_flag: false,
inbound,
client_session: client_session_id.map(ClientSessionContext::new),
// server_session_id must be generated randomly
server_session_id: generate_server_session_id(),
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<UdpAssociationSendMessage>) {
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 +399,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 +407,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 +442,16 @@ 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),
#[cfg(feature = "aead-cipher-2022")]
Some(ref s) => NatKey::SessionId(s.client_session_id),
#[cfg(not(feature = "aead-cipher-2022"))]
Some(..) => unreachable!("client_session_id is not None but aead-cipher-2022 is not enabled"),
};
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 +477,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<UdpSocketControlData>,
) {
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 +510,34 @@ impl UdpAssociationContext {
return;
}
if let Some(control) = control {
// Check if Packet ID is in the window
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(|| {
trace!(
"udp client {} with session {} created",
self.peer_addr,
control.client_session_id
);
ClientContext {
packet_window_filter: PacketWindowFilter::new(),
}
});
let packet_id = control.packet_id;
if !session_context
.packet_window_filter
.validate_packet_id(packet_id, u64::MAX)
{
error!("udp client {} packet_id {} out of window", self.peer_addr, packet_id);
return;
}
}
if let Err(err) = self.dispatch_received_outbound_packet(target_addr, data).await {
error!(
"udp relay {} -> {} with {} bytes, error: {}",
@@ -392,17 +601,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
);
}
}
}
}
}
+9 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "shadowsocks"
version = "1.14.3"
version = "1.15.0"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"
@@ -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", "lru_time_cache"]
# Enable detection against replay attack
security-replay-attack-detect = ["bloomfilter", "spin"]
# Enable IV printable prefix
@@ -56,6 +59,7 @@ pin-project = "1.0"
bloomfilter = { version = "1.0.8", optional = true }
thiserror = "1.0"
rand = { version = "0.8", optional = true }
lru_time_cache = { version = "0.11", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_urlencoded = "0.7"
@@ -72,11 +76,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"] }
+38 -4
View File
@@ -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,
};
@@ -175,6 +175,39 @@ pub struct ServerConfig {
weight: ServerWeight,
}
#[cfg(feature = "aead-cipher-2022")]
#[inline]
fn make_derived_key(method: CipherKind, password: &str, enc_key: &mut [u8]) {
if method.is_aead_2022() {
// AEAD 2022 password is a base64 form of enc_key
match base64::decode_config(password, base64::STANDARD) {
Ok(v) => {
if v.len() != enc_key.len() {
panic!(
"{} is expecting a {} bytes key, but password: {} ({} bytes after decode)",
method,
enc_key.len(),
password,
v.len()
);
}
enc_key.copy_from_slice(&v);
}
Err(err) => {
panic!("{} password {} is not base64 encoded, error: {}", method, password, err);
}
}
} else {
openssl_bytes_to_key(password.as_bytes(), enc_key);
}
}
#[cfg(not(feature = "aead-cipher-2022"))]
#[inline]
fn make_derived_key(_method: CipherKind, password: &str, enc_key: &mut [u8]) {
openssl_bytes_to_key(password.as_bytes(), enc_key);
}
impl ServerConfig {
/// Create a new `ServerConfig`
pub fn new<A, P>(addr: A, password: P, method: CipherKind) -> ServerConfig
@@ -185,7 +218,7 @@ impl ServerConfig {
let password = password.into();
let mut enc_key = vec![0u8; method.key_len()].into_boxed_slice();
openssl_bytes_to_key(password.as_bytes(), &mut enc_key);
make_derived_key(method, &password, &mut enc_key);
ServerConfig {
addr: addr.into(),
@@ -211,7 +244,7 @@ impl ServerConfig {
self.password = password.into();
let mut enc_key = vec![0u8; method.key_len()].into_boxed_slice();
openssl_bytes_to_key(self.password.as_bytes(), &mut enc_key);
make_derived_key(method, &self.password, &mut enc_key);
self.enc_key = enc_key;
}
@@ -399,7 +432,8 @@ impl ServerConfig {
}
};
let mut svrconfig = ServerConfig::new(addr, pwd.to_owned(), method.parse().unwrap());
let method = method.parse().expect("method");
let mut svrconfig = ServerConfig::new(addr, pwd.to_owned(), method);
if let Some(q) = parsed.query() {
let query = match serde_urlencoded::from_bytes::<Vec<(String, String)>>(q.as_bytes()) {
+14 -4
View File
@@ -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<S>(
&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<S>(
&mut self,
@@ -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<TcpCipher>,
buffer: BytesMut,
method: CipherKind,
salt: Option<Bytes>,
}
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<S>(
&mut self,
cx: &mut task::Context<'_>,
context: &Context,
stream: &mut S,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>>
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<S>(&mut self, cx: &mut task::Context<'_>, stream: &mut S, key: &[u8]) -> Poll<io::Result<()>>
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<S>(&mut self, cx: &mut task::Context<'_>, stream: &mut S) -> Poll<io::Result<Option<usize>>>
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<S>(
&mut self,
cx: &mut task::Context<'_>,
context: &Context,
stream: &mut S,
size: usize,
) -> Poll<io::Result<()>>
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<S>(&mut self, cx: &mut task::Context<'_>, stream: &mut S, size: usize) -> Poll<io::Result<usize>>
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<usize> {
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<S>(
&mut self,
cx: &mut task::Context<'_>,
stream: &mut S,
mut buf: &[u8],
) -> Poll<io::Result<usize>>
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();
}
}
}
}
}
@@ -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<S> CryptoStream<S> {
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<S> CryptoStream<S> {
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<S> CryptoStream<S> {
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<io::Result<()>>;
}
/// Cryptographic writer trait
pub trait CryptoWrite {
fn poll_write_encrypted(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>>;
}
impl<S> CryptoStream<S> {
@@ -182,19 +254,44 @@ impl<S> CryptoStream<S> {
}
}
impl<S> CryptoStream<S>
impl<S> CryptoRead for CryptoStream<S>
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<io::Result<()>> {
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<S> CryptoWrite for CryptoStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
/// Attempt to write encrypted data to `stream`
#[inline]
fn poll_write_encrypted(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let CryptoStream {
ref mut enc,
ref mut stream,
..
} = *self;
enc.poll_write_encrypted(cx, stream, buf)
}
}
@@ -202,12 +299,6 @@ impl<S> CryptoStream<S>
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<io::Result<usize>> {
self.enc.poll_write_encrypted(cx, &mut self.stream, buf)
}
/// Polls `flush` on the underlying stream
#[inline]
pub fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
@@ -257,18 +348,33 @@ impl<S> CryptoStreamReadHalf<S> {
}
impl<S> CryptoStreamReadHalf<S>
where
S: AsyncRead + Unpin,
{
/// Get received IV (Stream) or Salt (AEAD, AEAD2022)
pub fn nonce(&self) -> Option<&[u8]> {
self.dec.nonce()
}
}
impl<S> CryptoRead for CryptoStreamReadHalf<S>
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<io::Result<()>> {
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<S> CryptoStreamWriteHalf<S> {
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<S> CryptoWrite for CryptoStreamWriteHalf<S>
where
S: AsyncWrite + Unpin,
{
/// Attempt to write encrypted data to `stream`
#[inline]
fn poll_write_encrypted(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let CryptoStreamWriteHalf {
ref mut enc,
ref mut writer,
..
} = *self;
enc.poll_write_encrypted(cx, writer, buf)
}
}
impl<S> CryptoStreamWriteHalf<S>
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<io::Result<usize>> {
self.enc.poll_write_encrypted(cx, &mut self.writer, buf)
}
/// Polls `flush` on the underlying stream
#[inline]
pub fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
@@ -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,
}
@@ -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,
};
@@ -16,13 +16,16 @@ use tokio::{
time,
};
#[cfg(feature = "aead-cipher-2022")]
use crate::context::Context;
use crate::{
config::ServerConfig,
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 +35,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<S> {
#[pin]
stream: CryptoStream<S>,
state: ProxyClientStreamWriteState,
writer_state: ProxyClientStreamWriteState,
reader_state: ProxyClientStreamReadState,
context: SharedContext,
}
@@ -141,9 +151,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,42 +186,205 @@ where
}
}
#[cfg(feature = "aead-cipher-2022")]
fn poll_read_aead_2022_header<S>(
context: &Context,
mut stream: Pin<&mut CryptoStream<S>>,
cx: &mut task::Context<'_>,
header_buf: &mut BytesMut,
header_pos: &mut usize,
) -> Poll<io::Result<()>>
where
S: AsyncRead + AsyncWrite + Unpin,
{
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;
}
while *header_pos < header_buf.len() {
let remaining_buf = &mut header_buf[*header_pos..];
let mut read_buf = ReadBuf::new(remaining_buf);
ready!(stream.as_mut().poll_read_decrypted(cx, context, &mut read_buf))?;
*header_pos += read_buf.filled().len();
}
// 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();
}
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 now.abs_diff(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 = &header_buf[..];
if salt != stream.sent_nonce() {
return Err(io::Error::new(
ErrorKind::Other,
"received TCP response header with unmatched salt",
))
.into();
}
Ok(()).into()
}
impl<S> AsyncRead for ProxyClientStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
#[inline]
fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
#[allow(unused_mut)]
let mut this = self.project();
this.stream.poll_read_decrypted(cx, this.context, buf)
#[allow(clippy::never_loop)]
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;
}
}
}
}
}
#[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<SmallRng> = RefCell::new(SmallRng::from_entropy());
}
let padding_size = if buf.is_empty() {
PADDING_RNG.with(|rng| rng.borrow_mut().gen::<usize>() % 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 {
unsafe {
buffer.advance_mut(padding_size);
}
}
}
let _ = method;
// 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<S> AsyncWrite for ProxyClientStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll<Result<usize, io::Error>> {
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.stream.poll_write_encrypted(cx, buffer))?;
@@ -207,7 +392,7 @@ where
// 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
@@ -236,117 +421,3 @@ where
self.project().stream.poll_shutdown(cx)
}
}
impl<S> ProxyClientStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
/// Splits into reader and writer halves
pub fn into_split(self) -> (ProxyClientStreamReadHalf<S>, ProxyClientStreamWriteHalf<S>) {
// 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,
},
)
}
}
/// Owned read half produced by `ProxyClientStream::into_split`
#[pin_project]
pub struct ProxyClientStreamReadHalf<S> {
#[pin]
reader: CryptoStreamReadHalf<S>,
context: SharedContext,
}
impl<S> AsyncRead for ProxyClientStreamReadHalf<S>
where
S: AsyncRead + Unpin,
{
#[inline]
fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
this.reader.poll_read_decrypted(cx, this.context, buf)
}
}
/// Owned write half produced by `ProxyClientStream::into_split`
#[pin_project]
pub struct ProxyClientStreamWriteHalf<S> {
#[pin]
writer: CryptoStreamWriteHalf<S>,
state: ProxyClientStreamWriteState,
}
impl<S> AsyncWrite for ProxyClientStreamWriteHalf<S>
where
S: AsyncWrite + Unpin,
{
fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll<Result<usize, io::Error>> {
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.writer.poll_write_encrypted(cx, buffer))?;
// In general, poll_write_encrypted should perform like write_all.
debug_assert!(n == buffer.len());
*(this.state) = ProxyClientStreamWriteState::Connected;
// 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.writer.poll_write_encrypted(cx, buf);
}
}
}
}
#[inline]
fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().writer.poll_flush(cx)
}
#[inline]
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().writer.poll_shutdown(cx)
}
}
@@ -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;
@@ -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<R: AsyncRead + Unpin>(method: CipherKind, reader: &mut R) -> io::Result<TcpRequestHeader> {
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<B: BufMut>(&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<B: BufMut>(&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<R: AsyncRead + Unpin>(method: CipherKind, reader: &mut R) -> io::Result<TcpResponseHeader> {
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<B: BufMut>(&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<B: BufMut>(&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(),
}
}
}
@@ -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<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<StreamTcpRequestHeader> {
Ok(StreamTcpRequestHeader {
addr: Address::read_from(reader).await?,
})
}
pub fn write_to_buf<B: BufMut>(&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<B: BufMut>(&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<R: AsyncRead + Unpin>(_reader: &mut R) -> io::Result<StreamTcpResponseHeader> {
Ok(StreamTcpResponseHeader)
}
pub fn write_to_buf<B: BufMut>(&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<B: BufMut>(&self, _buf: &mut B) {}
pub fn serialized_len(&self) -> usize {
0
}
}
@@ -0,0 +1,235 @@
//! 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_size: u16,
}
impl Aead2022TcpRequestHeader {
pub async fn read_from<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Aead2022TcpRequestHeader> {
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 now.abs_diff(timestamp) > SERVER_STREAM_TIMESTAMP_MAX_DIFF {
return Err(io::Error::new(
ErrorKind::Other,
format!("received TCP request header with aged timestamp: {}", timestamp),
));
}
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);
if padding_size > 0 {
let mut take_reader = reader.take(padding_size as u64);
let mut buffer = [0u8; 64];
loop {
match take_reader.read(&mut buffer).await {
Ok(0) => break,
Ok(..) => continue,
Err(err) => return Err(err),
}
}
}
Ok(Aead2022TcpRequestHeader {
addr,
timestamp,
padding_size,
})
}
pub fn write_to_buf<B: BufMut>(&self, buf: &mut B) {
Aead2022TcpRequestHeaderRef {
addr: &self.addr,
timestamp: self.timestamp,
padding_size: self.padding_size,
}
.write_to_buf(buf)
}
pub fn serialized_len(&self) -> usize {
Aead2022TcpRequestHeaderRef {
addr: &self.addr,
timestamp: self.timestamp,
padding_size: self.padding_size,
}
.serialized_len()
}
}
pub struct Aead2022TcpRequestHeaderRef<'a> {
pub addr: &'a Address,
pub timestamp: u64,
pub padding_size: u16,
}
impl<'a> Aead2022TcpRequestHeaderRef<'a> {
pub fn write_to_buf<B: BufMut>(&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_size as usize <= MAX_PADDING_SIZE,
"padding length must be in [0, {}]",
MAX_PADDING_SIZE
);
buf.put_u16(self.padding_size);
if self.padding_size > 0 {
unsafe {
buf.advance_mut(self.padding_size as usize);
}
}
}
pub fn serialized_len(&self) -> usize {
1 + 8 + self.addr.serialized_len() + 2 + self.padding_size as usize
}
}
/// AEAD 2022 TCP Response Header
///
/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+
/// | TYPE | UNIX TIMESTAMP |
/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+
/// | Request SALT (Variable ...)
/// +-------+-------+-------+-------+-------+-------+-------+-------+-------+
pub struct Aead2022TcpResponseHeader {
pub timestamp: u64,
pub request_salt: Vec<u8>,
}
impl Aead2022TcpResponseHeader {
pub async fn read_from<R: AsyncRead + Unpin>(
method: CipherKind,
reader: &mut R,
) -> io::Result<Aead2022TcpResponseHeader> {
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 now.abs_diff(timestamp) > SERVER_STREAM_TIMESTAMP_MAX_DIFF {
return Err(io::Error::new(
ErrorKind::Other,
format!("received TCP response header with aged timestamp: {}", timestamp),
));
}
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<B: BufMut>(&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<B: BufMut>(&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()
}
}
@@ -6,21 +6,39 @@ use std::{
task::{self, Poll},
};
#[cfg(feature = "aead-cipher-2022")]
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<std::task::Waker>),
#[cfg(feature = "aead-cipher-2022")]
WriteHeader(BytesMut, usize),
Established,
}
/// A stream for communicating with shadowsocks' proxy client
#[pin_project]
pub struct ProxyServerStream<S> {
#[pin]
stream: CryptoStream<S>,
context: SharedContext,
writer_state: ProxyServerStreamWriteState,
}
impl<S> ProxyServerStream<S> {
@@ -30,9 +48,20 @@ impl<S> ProxyServerStream<S> {
method: CipherKind,
key: &[u8],
) -> ProxyServerStream<S> {
#[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 +85,13 @@ impl<S> ProxyServerStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
/// Splits into reader and writer halves
pub fn into_split(self) -> (ProxyServerStreamReadHalf<S>, ProxyServerStreamWriteHalf<S>) {
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<Address> {
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 +101,18 @@ where
{
#[inline]
fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
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,7 +122,57 @@ where
{
#[inline]
fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll<Result<usize, io::Error>> {
self.project().stream.poll_write_encrypted(cx, buf)
#[allow(unused_mut)]
let mut this = self.project();
#[allow(clippy::never_loop)]
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]
@@ -100,49 +185,3 @@ where
self.project().stream.poll_shutdown(cx)
}
}
/// Owned read half produced by `ProxyServerStream::into_split`
#[pin_project]
pub struct ProxyServerStreamReadHalf<S> {
#[pin]
reader: CryptoStreamReadHalf<S>,
context: SharedContext,
}
impl<S> AsyncRead for ProxyServerStreamReadHalf<S>
where
S: AsyncRead + Unpin,
{
#[inline]
fn poll_read(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
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<S> {
#[pin]
writer: CryptoStreamWriteHalf<S>,
}
impl<S> AsyncWrite for ProxyServerStreamWriteHalf<S>
where
S: AsyncWrite + Unpin,
{
#[inline]
fn poll_write(self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll<Result<usize, io::Error>> {
self.project().writer.poll_write_encrypted(cx, buf)
}
#[inline]
fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().writer.poll_flush(cx)
}
#[inline]
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().writer.poll_shutdown(cx)
}
}
@@ -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<Cipher>,
buffer: BytesMut,
method: CipherKind,
iv: Option<Bytes>,
}
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<S>(
&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<S>(
&mut self,
@@ -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,
}
}
@@ -0,0 +1,120 @@
//! 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);
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)
}
}
}
@@ -0,0 +1,514 @@
//! 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,
cmp::Ordering,
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
io::{self, Cursor, ErrorKind, Seek, SeekFrom},
rc::Rc,
slice,
time::{Duration, SystemTime},
};
use aes::{
cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
Aes128,
Aes256,
Block,
};
use byte_string::ByteStr;
use bytes::{Buf, BufMut, BytesMut};
use log::{error, trace};
use lru_time_cache::LruCache;
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;
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
struct CipherKey {
method: CipherKind,
key: usize,
session_id: u64,
}
impl PartialOrd for CipherKey {
fn partial_cmp(&self, other: &CipherKey) -> Option<Ordering> {
let hash1 = {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
};
let hash2 = {
let mut hasher = DefaultHasher::new();
other.hash(&mut hasher);
hasher.finish()
};
hash1.partial_cmp(&hash2)
}
}
impl Ord for CipherKey {
fn cmp(&self, other: &CipherKey) -> Ordering {
let hash1 = {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
};
let hash2 = {
let mut hasher = DefaultHasher::new();
other.hash(&mut hasher);
hasher.finish()
};
hash1.cmp(&hash2)
}
}
thread_local! {
static PADDING_RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
static CIPHER_CACHE: RefCell<LruCache<CipherKey, Rc<UdpCipher>>> =
RefCell::new(LruCache::with_expiry_duration_and_capacity(Duration::from_secs(60), 102400));
}
#[inline]
fn get_padding_size(payload: &[u8]) -> usize {
if payload.is_empty() {
PADDING_RNG.with(|rng| rng.borrow_mut().gen::<usize>() % MAX_PADDING_SIZE)
} else {
0
}
}
#[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 get_cipher(method: CipherKind, key: &[u8], session_id: u64) -> Rc<UdpCipher> {
CIPHER_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
let cache_key = CipherKey {
method,
// The key is stored in ServerConfig structure, so the address of it won't change.
key: key.as_ptr() as usize,
session_id,
};
cache
.entry(cache_key)
.or_insert_with(|| Rc::new(UdpCipher::new(method, key, session_id)))
.clone()
})
}
fn encrypt_message(_context: &Context, method: CipherKind, key: &[u8], packet: &mut BytesMut, session_id: u64) {
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 cipher = get_cipher(method, key, session_id);
let (nonce, message) = packet.split_at_mut(nonce_size);
cipher.encrypt_packet(nonce, message);
}
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 cipher = get_cipher(method, key, session_id);
// Encrypt the rest of the packet with AEAD cipher (AES-*-GCM)
let (packet_header, message) = packet.split_at_mut(16);
let nonce = &packet_header[4..16];
cipher.encrypt_packet(nonce, message);
// [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),
}
}
_ => unreachable!("{} is not an AEAD 2022 cipher", method),
}
}
fn decrypt_message(context: &Context, 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 (nonce, message) = packet.split_at_mut(nonce_size);
if let Err(..) = context.check_nonce_replay(nonce) {
error!("detected replayed nonce: {:?}", ByteStr::new(nonce));
return false;
}
// NOTE: ChaCha20-Poly1305's session_id is not required because it uses PSK directly
//
// But still, we get the session_id for cache
let session_id = {
let session_id_buf = &message[0..8];
let session_id_slice: &[u64] = unsafe { slice::from_raw_parts(session_id_buf.as_ptr() as *const _, 1) };
u64::from_be(session_id_slice[0])
};
let cipher = get_cipher(method, key, session_id);
if !cipher.decrypt_packet(nonce, message) {
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
let (packet_header, message) = packet.split_at_mut(16);
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(packet_header);
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(packet_header);
cipher.decrypt_block(block);
}
_ => unreachable!("{} is not an AES-*-GCM cipher", method),
}
// Session ID is the first 64-bits
let session_id = {
let session_id_buf = &packet_header[0..8];
let session_id_slice: &[u64] = unsafe { slice::from_raw_parts(session_id_buf.as_ptr() as *const _, 1) };
u64::from_be(session_id_slice[0])
};
let nonce = &packet_header[4..16];
let cipher = {
if let Err(..) = context.check_nonce_replay(nonce) {
error!("detected replayed nonce: {:?}", ByteStr::new(nonce));
return false;
}
get_cipher(method, key, session_id)
};
if !cipher.decrypt_packet(nonce, message) {
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 {
unsafe {
dst.advance_mut(nonce_size);
}
let nonce = &mut dst[..nonce_size];
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 {
unsafe {
dst.advance_mut(padding_size);
}
}
addr.write_to_buf(dst);
dst.put_slice(payload);
encrypt_message(context, 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(context, 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 now.abs_diff(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 + 8 + 2 + padding_size + addr.serialized_len() + payload.len() + method.tag_len(),
);
// Generate IV
if nonce_size > 0 {
unsafe {
dst.advance_mut(nonce_size);
}
let nonce = &mut dst[..nonce_size];
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 {
unsafe {
dst.advance_mut(padding_size);
}
}
addr.write_to_buf(dst);
dst.put_slice(payload);
encrypt_message(context, 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(context, 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 now.abs_diff(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))
}
@@ -21,112 +21,97 @@
//! ```
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,
) {
match method.category() {
CipherCategory::None => {
let _ = control;
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),
CipherCategory::Stream => {
let _ = control;
encrypt_payload_stream(context, method, key, addr, payload, dst)
}
CipherCategory::Aead => {
let _ = control;
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));
match method.category() {
CipherCategory::None => {
let _ = control;
dst.reserve(addr.serialized_len() + payload.len());
addr.write_to_buf(dst);
dst.put_slice(payload);
}
#[cfg(feature = "stream-cipher")]
CipherCategory::Stream => {
let _ = control;
encrypt_payload_stream(context, method, key, addr, payload, dst)
}
CipherCategory::Aead => {
let _ = control;
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 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());
}
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<UdpSocketControlData>)> {
match method.category() {
CipherCategory::None => {
let mut cur = Cursor::new(payload);
@@ -135,7 +120,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 +129,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,
/// 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 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,
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<UdpSocketControlData>)> {
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))),
}
}
@@ -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)
///
@@ -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,
}
@@ -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<ConnectOpts> = Lazy::new(Default::default);
static DEFAULT_SOCKET_CONTROL: Lazy<UdpSocketControlData> = 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<usize> {
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<usize> {
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<A: ToSocketAddrs>(&self, target: A, addr: &Address, payload: &[u8]) -> io::Result<usize> {
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<A: ToSocketAddrs>(
&self,
target: A,
addr: &Address,
control: &UdpSocketControlData,
payload: &[u8],
) -> io::Result<usize> {
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<UdpSocketControlData>)> {
// 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<UdpSocketControlData>)> {
// 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
@@ -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)
}
}
}
+38 -4
View File
@@ -16,7 +16,7 @@ use tokio::{
use shadowsocks::{
config::{ServerConfig, ServerType},
context::Context,
crypto::v1::CipherKind,
crypto::CipherKind,
relay::{
socks5::Address,
tcprelay::{
@@ -32,7 +32,7 @@ async fn handle_tcp_tunnel_server_client(
method: CipherKind,
mut stream: ProxyServerStream<TcpStream>,
) -> io::Result<()> {
let addr = Address::read_from(&mut stream).await?;
let addr = stream.handshake().await?;
let mut remote = {
let remote = match addr {
@@ -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);
@@ -185,3 +185,37 @@ async fn tcp_tunnel_none() {
.await
.unwrap();
}
#[cfg(feature = "aead-cipher-2022")]
#[tokio::test]
async fn tcp_tunnel_aead_2022_aes() {
let _ = env_logger::try_init();
let server_addr = "127.0.0.1:34001".parse::<SocketAddr>().unwrap();
let local_addr = "127.0.0.1:34101".parse::<SocketAddr>().unwrap();
tcp_tunnel_example(
server_addr,
local_addr,
"3L69X4PF2eSL/JSLkoWnXg==",
CipherKind::AEAD2022_BLAKE3_AES_128_GCM,
)
.await
.unwrap();
}
#[cfg(feature = "aead-cipher-2022")]
#[tokio::test]
async fn tcp_tunnel_aead_2022_chacha20() {
let _ = env_logger::try_init();
let server_addr = "127.0.0.1:35001".parse::<SocketAddr>().unwrap();
let local_addr = "127.0.0.1:35101".parse::<SocketAddr>().unwrap();
tcp_tunnel_example(
server_addr,
local_addr,
"VUw3mGWIpil2z2DKiyauE2Sp9KyE2ab8dulciawe74o",
CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305,
)
.await
.unwrap();
}
+2 -2
View File
@@ -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);
+41 -1
View File
@@ -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},
};
@@ -180,3 +180,43 @@ async fn udp_tunnel_none() {
.await
.unwrap();
}
#[cfg(feature = "aead-cipher-2022")]
#[tokio::test]
async fn udp_tunnel_aead_2022_aes() {
let _ = env_logger::try_init();
let server_addr = "127.0.0.1:24001".parse::<SocketAddr>().unwrap();
let local_addr = "127.0.0.1:24101".parse::<SocketAddr>().unwrap();
let target_addr = "127.0.0.1:24201".parse::<SocketAddr>().unwrap();
udp_tunnel_echo(
server_addr,
local_addr,
target_addr,
"D1HJFfvRIxpklHLeKvjCDQ==",
CipherKind::AEAD2022_BLAKE3_AES_128_GCM,
)
.await
.unwrap();
}
#[cfg(feature = "aead-cipher-2022")]
#[tokio::test]
async fn udp_tunnel_aead_2022_chacha20() {
let _ = env_logger::try_init();
let server_addr = "127.0.0.1:25001".parse::<SocketAddr>().unwrap();
let local_addr = "127.0.0.1:25101".parse::<SocketAddr>().unwrap();
let target_addr = "127.0.0.1:25201".parse::<SocketAddr>().unwrap();
udp_tunnel_echo(
server_addr,
local_addr,
target_addr,
"4wYfDniq4N6kMqFajRO03PPZLfPkl469eNYY9Wz0E78=",
CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305,
)
.await
.unwrap();
}
+1 -1
View File
@@ -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,
},
};
+1 -1
View File
@@ -15,7 +15,7 @@ use shadowsocks_service::{
run_manager,
shadowsocks::{
config::{ManagerAddr, Mode},
crypto::v1::{available_ciphers, CipherKind},
crypto::{available_ciphers, CipherKind},
plugin::PluginConfig,
},
};
+1 -1
View File
@@ -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,
},
};
+1 -1
View File
@@ -17,7 +17,7 @@ use shadowsocks_service::{
run_server,
shadowsocks::{
config::{ServerAddr, ServerConfig},
crypto::v1::CipherKind,
crypto::CipherKind,
},
};
+1 -1
View File
@@ -17,7 +17,7 @@ use shadowsocks_service::{
run_server,
shadowsocks::{
config::{Mode, ServerAddr, ServerConfig},
crypto::v1::CipherKind,
crypto::CipherKind,
relay::socks5::Address,
},
};
+1 -1
View File
@@ -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";