mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-06 08:14:07 +00:00
AEAD-2022 ciphers (new protocol) (#811)
This commit is contained in:
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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::{
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ use shadowsocks_service::{
|
||||
run_manager,
|
||||
shadowsocks::{
|
||||
config::{ManagerAddr, Mode},
|
||||
crypto::v1::{available_ciphers, CipherKind},
|
||||
crypto::{available_ciphers, CipherKind},
|
||||
plugin::PluginConfig,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
@@ -17,7 +17,7 @@ use shadowsocks_service::{
|
||||
run_server,
|
||||
shadowsocks::{
|
||||
config::{ServerAddr, ServerConfig},
|
||||
crypto::v1::CipherKind,
|
||||
crypto::CipherKind,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ use shadowsocks_service::{
|
||||
run_server,
|
||||
shadowsocks::{
|
||||
config::{Mode, ServerAddr, ServerConfig},
|
||||
crypto::v1::CipherKind,
|
||||
crypto::CipherKind,
|
||||
relay::socks5::Address,
|
||||
},
|
||||
};
|
||||
|
||||
+1
-1
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user