diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6af70648..0cb7c6fa 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -43,8 +43,12 @@ jobs: run: cargo test --verbose --no-default-features --no-fail-fast - name: Build & Test (--no-default-features) - shadowsocks run: cargo test --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --no-default-features --no-fail-fast - - name: Build with All Features Enabled - run: cargo build --verbose --features "local-http-rustls local-redir local-dns dns-over-tls dns-over-https stream-cipher" + - 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" + - 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" - name: Build with All Features Enabled - shadowsocks run: cargo build --manifest-path ./crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher" - name: Clippy Check diff --git a/.github/workflows/build-nightly-release.yml b/.github/workflows/build-nightly-release.yml index 3226c3be..702a8c3f 100644 --- a/.github/workflows/build-nightly-release.yml +++ b/.github/workflows/build-nightly-release.yml @@ -38,7 +38,7 @@ jobs: build-unix: runs-on: ${{ matrix.os }} env: - BUILD_EXTRA_FEATURES: "local-redir" + BUILD_EXTRA_FEATURES: "local-redir local-tun" RUST_BACKTRACE: full strategy: matrix: diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 9cab8f41..e13a94d4 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -52,7 +52,7 @@ jobs: build-unix: runs-on: ${{ matrix.os }} env: - BUILD_EXTRA_FEATURES: "local-redir" + BUILD_EXTRA_FEATURES: "local-redir local-tun" RUST_BACKTRACE: full strategy: matrix: diff --git a/Cargo.lock b/Cargo.lock index 37278e85..a70a89ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" +checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" [[package]] name = "arc-swap" @@ -317,6 +317,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "etherparse" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa20922281f9ee5ffcda45e80d56085829279f1270f79fbabc39809a4354807" +dependencies = [ + "byteorder", +] + [[package]] name = "exitcode" version = "1.1.2" @@ -331,13 +340,13 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "filetime" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "winapi", ] @@ -503,9 +512,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" +checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" dependencies = [ "bytes", "fnv", @@ -568,9 +577,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" dependencies = [ "bytes", "http", @@ -579,9 +588,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "httpdate" @@ -669,6 +678,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ioctl-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c429fffa658f288669529fc26565f728489a2e39bc7b24a428aaaf51355182e" + [[package]] name = "ipconfig" version = "0.2.2" @@ -725,9 +740,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d" dependencies = [ "wasm-bindgen", ] @@ -745,9 +760,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512705bfcaeb3d46379771adc69deab978355fc68fdf960f9fb11abc8d678a96" +checksum = "058a107a784f8be94c7d35c1300f4facced2e93d2fbe5b1452b44e905ddca4a9" dependencies = [ "kqueue-sys", "libc", @@ -755,9 +770,9 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9803ae382091c10a5c7297ffb9fde284dbc9662b249f86eacef605d97ae92d99" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ "bitflags", "libc", @@ -771,9 +786,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" +checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" [[package]] name = "libmimalloc-sys" @@ -871,15 +886,15 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" @@ -954,9 +969,9 @@ dependencies = [ [[package]] name = "notify" -version = "5.0.0-pre.11" +version = "5.0.0-pre.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c614e7ed2b1cf82ec99aeffd8cf6225ef5021b9951148eb161393c394855032c" +checksum = "20a629259bb2c87a884bb76f6086c8637919de6d074754341c12e5dd3aed6326" dependencies = [ "bitflags", "crossbeam-channel", @@ -1022,9 +1037,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openssl" -version = "0.10.35" +version = "0.10.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" +checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" dependencies = [ "bitflags", "cfg-if", @@ -1042,9 +1057,9 @@ checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" [[package]] name = "openssl-sys" -version = "0.9.65" +version = "0.9.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" +checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" dependencies = [ "autocfg", "cc", @@ -1082,7 +1097,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "smallvec", "winapi", ] @@ -1273,9 +1288,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -1442,9 +1457,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.126" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834" dependencies = [ "serde_derive", ] @@ -1461,9 +1476,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4" dependencies = [ "proc-macro2", "quote", @@ -1495,9 +1510,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +checksum = "039ba818c784248423789eec090aab9fb566c7b94d6ebbfa1814a9fd52c8afb2" dependencies = [ "dtoa", "linked-hash-map", @@ -1519,7 +1534,7 @@ dependencies = [ [[package]] name = "shadowsocks" -version = "1.11.3" +version = "1.12.0" dependencies = [ "arc-swap 1.3.0", "async-trait", @@ -1564,7 +1579,7 @@ dependencies = [ [[package]] name = "shadowsocks-rust" -version = "1.11.3" +version = "1.12.0" dependencies = [ "byte_string", "byteorder", @@ -1574,6 +1589,7 @@ dependencies = [ "env_logger", "exitcode", "futures", + "ipnet", "jemallocator", "log", "log4rs", @@ -1588,7 +1604,7 @@ dependencies = [ [[package]] name = "shadowsocks-service" -version = "1.11.3" +version = "1.12.0" dependencies = [ "async-trait", "byte_string", @@ -1596,6 +1612,7 @@ dependencies = [ "bytes", "cfg-if", "env_logger", + "etherparse", "futures", "hyper", "ipnet", @@ -1624,6 +1641,7 @@ dependencies = [ "tokio-rustls", "tower", "trust-dns-resolver", + "tun", "webpki-roots", "winapi", ] @@ -1639,15 +1657,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" +checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1" [[package]] name = "slab" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" +checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "smallvec" @@ -1717,9 +1735,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" dependencies = [ "proc-macro2", "quote", @@ -1750,7 +1768,7 @@ dependencies = [ "cfg-if", "libc", "rand", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "remove_dir_all", "winapi", ] @@ -1842,9 +1860,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c" +checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b" dependencies = [ "autocfg", "bytes", @@ -1967,9 +1985,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8" dependencies = [ "lazy_static", ] @@ -2078,6 +2096,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tun" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde2c6cb9cc8643fda7ef1b093de76a682bccd6b2868eeea7e96afde072c44fd" +dependencies = [ + "ioctl-sys", + "libc", + "thiserror", +] + [[package]] name = "typemap" version = "0.3.3" @@ -2101,12 +2130,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" [[package]] name = "unicode-normalization" @@ -2203,9 +2229,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2213,9 +2239,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041" dependencies = [ "bumpalo", "lazy_static", @@ -2228,9 +2254,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2238,9 +2264,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad" dependencies = [ "proc-macro2", "quote", @@ -2251,15 +2277,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" +checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29" [[package]] name = "web-sys" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 6770e0ba..00989edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-rust" -version = "1.11.3" +version = "1.12.0" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" @@ -92,6 +92,8 @@ local-redir = ["local", "shadowsocks-service/local-redir"] local-tunnel = ["local", "shadowsocks-service/local-tunnel"] # Enable socks4 protocol for sslocal local-socks4 = ["local", "shadowsocks-service/local-socks4"] +# Enable Tun interface protocol for sslocal +local-tun = ["local", "shadowsocks-service/local-tun", "ipnet"] # Enable jemalloc for binaries jemalloc = ["jemallocator"] @@ -126,13 +128,15 @@ exitcode = "1" futures = "0.3" tokio = { version = "1", features = ["rt", "signal"] } +ipnet = { version = "2.3", optional = true } + mimalloc = { version = "0.1", optional = true } tcmalloc = { version = "0.3", optional = true } jemallocator = { version = "0.3", optional = true } snmalloc-rs = { version = "0.2", optional = true } rpmalloc = { version = "0.2", optional = true } -shadowsocks-service = { version = "1.11.3", path = "./crates/shadowsocks-service" } +shadowsocks-service = { version = "1.12.0", path = "./crates/shadowsocks-service" } [target.'cfg(unix)'.dependencies] daemonize = "0.4" diff --git a/README.md b/README.md index 6cf6df4a..11a18039 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,6 @@ sslocal -b "127.0.0.1:1080" --server-url "ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ@127.0 ### HTTP Local client ```bash -# Read local client configuration from file sslocal -b "127.0.0.1:3128" --protocol http -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" ``` @@ -261,7 +260,6 @@ All parameters are the same as Socks5 client, except `--protocol http`. ### Tunnel Local client ```bash -# Read local client configuration from file # Set 127.0.0.1:8080 as the target for forwarding to sslocal --protocol tunnel -b "127.0.0.1:3128" -f "127.0.0.1:8080" -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" ``` @@ -277,7 +275,6 @@ sslocal --protocol tunnel -b "127.0.0.1:3128" -f "127.0.0.1:8080" -s "[::1]:8388 * BSDs (with `pf`), such as OS X 10.10+, FreeBSD, ... ```bash -# Read local client configuration from file sslocal -b "127.0.0.1:60080" --protocol redir -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" --tcp-redir "redirect" --udp-redir "tproxy" ``` @@ -287,6 +284,45 @@ Redirects connections with `iptables` configurations to the port that `sslocal` * (optional) `--tcp-redir` sets TCP mode to `REDIRECT` (Linux) * (optional) `--udp-redir` sets UDP mode to `TPROXY` (Linux) +### Tun interface client + +**NOTE**: It currently only supports + +* Linux, Android +* macOS, iOS + +#### Linux + +Create a Tun interface with name `tun0` + +```bash +ip tuntap add mode tun tun0 +ifconfig tun0 inet 10.255.0.1 netmask 255.255.255.0 up +``` + +Start `sslocal` with `--protocol tun` and binds to `tun0` + +```bash +sslocal --protocol tun -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" --outbound-bind-interface lo0 --tun-interface-name tun0 +``` + +#### macOS + +```bash +sslocal --protocol tun -s "[::1]:8388" -m "aes-256-gcm" -k "hello-kitty" --outbound-bind-interface lo0 --tun-interface-address 10.255.0.1/24 +``` + +It will create a Tun interface with address `10.255.0.1` and netmask `255.255.255.0`. + +(OPTIONAL) macOS requires adding a route entry to redirect packets that destinated to `10.155.0.1` (the address of the Tun interface) to the Tun interface itself. + +```bash +# 10.255.0.1 address +# 255.255.255.0 netmask +# utun8 tun's interface name +route add -net 10.255.0.1 -netmask 255.255.255.0 -interface utun8 +``` + ### Server ```bash @@ -422,6 +458,16 @@ Example configuration: "remote_dns_address": "8.8.8.8", // OPTIONAL. Remote DNS's port, 53 by default "remote_dns_port": 53 + }, + { + // Tun local server (feature = "local-tun") + "protocol": "tun", + // Tun interface name + "tun_interface_name": "tun0", + // Tun interface address + // + // It has to be a host address in CIDR form + "tun_interface_address": "10.255.0.1/24" } ], diff --git a/bin/common/validator/mod.rs b/bin/common/validator/mod.rs index 8712f371..29e539e6 100644 --- a/bin/common/validator/mod.rs +++ b/bin/common/validator/mod.rs @@ -4,6 +4,8 @@ use std::net::{IpAddr, SocketAddr}; +#[cfg(feature = "local-tun")] +use ipnet::IpNet; #[cfg(feature = "local-dns")] use shadowsocks_service::local::dns::NameServerAddr; use shadowsocks_service::shadowsocks::{relay::socks5::Address, ManagerAddr, ServerAddr, ServerConfig}; @@ -48,3 +50,17 @@ pub fn validate_server_url(v: String) -> Result<(), String> { Err(..) => Err("should be SIP002 (https://shadowsocks.org/en/wiki/SIP002-URI-Scheme.html) format".to_owned()), } } + +#[cfg(feature = "local-tun")] +pub fn validate_ipnet(v: String) -> Result<(), String> { + match v.parse::() { + Err(..) => Err("should be a CIDR address like 10.1.2.3/24".to_owned()), + Ok(n) => { + if n.trunc() == n { + Err("should be a valid CIDR address with a host like 10.1.2.3/24".to_owned()) + } else { + Ok(()) + } + } + } +} diff --git a/bin/sslocal.rs b/bin/sslocal.rs index 9640c938..4958e129 100644 --- a/bin/sslocal.rs +++ b/bin/sslocal.rs @@ -4,9 +4,9 @@ //! or you could specify a configuration file. The format of configuration file is defined //! in mod `config`. -use std::{process, time::Duration}; +use std::{net::IpAddr, process, time::Duration}; -use clap::{clap_app, Arg}; +use clap::{clap_app, Arg, Error as ClapError, ErrorKind as ClapErrorKind}; use futures::future::{self, Either}; use log::info; use tokio::{self, runtime::Builder}; @@ -41,12 +41,12 @@ fn main() { (version: VERSION) (about: "A fast tunnel proxy that helps you bypass firewalls.") - (@arg CONFIG: -c --config +takes_value required_unless_all(&["LOCAL_ADDR", "SERVER_CONFIG"]) "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") + (@arg CONFIG: -c --config +takes_value required_unless("SERVER_CONFIG") "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") (@arg LOCAL_ADDR: -b --("local-addr") +takes_value {validator::validate_server_addr} "Local address, listen only to this address if specified") (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] requires[LOCAL_ADDR] "Server mode UDP_ONLY") (@arg TCP_AND_UDP: -U requires[LOCAL_ADDR] "Server mode TCP_AND_UDP") - (@arg PROTOCOL: --protocol +takes_value requires[LOCAL_ADDR] possible_values(ProtocolType::available_protocols()) "Protocol for communicating with clients (SOCKS5 by default)") + (@arg PROTOCOL: --protocol +takes_value possible_values(ProtocolType::available_protocols()) "Protocol for communicating with clients (SOCKS5 by default)") (@arg UDP_BIND_ADDR: --("udp-bind-addr") +takes_value requires[LOCAL_ADDR] {validator::validate_server_addr} "UDP relay's bind address, default is the same as local-addr") (@arg SERVER_ADDR: -s --("server-addr") +takes_value {validator::validate_server_addr} requires[PASSWORD ENCRYPT_METHOD] "Server address") @@ -76,6 +76,8 @@ fn main() { (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") + + (@arg OUTBOUND_BIND_ADDR: --("outbound-bind-addr") +takes_value alias("bind-addr") {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") ); // FIXME: -6 is not a identifier, so we cannot build it with clap_app! @@ -168,6 +170,21 @@ fn main() { } } + #[cfg(feature = "local-tun")] + { + app = clap_app!(@app (app) + (@arg TUN_INTERFACE_NAME: --("tun-interface-name") +takes_value "Tun interface name, allocate one if not specify") + (@arg TUN_INTERFACE_ADDRESS: --("tun-interface-address") +takes_value {validator::validate_ipnet} "Tun interface address (network)") + ); + + #[cfg(unix)] + { + app = clap_app!(@app (app) + (@arg TUN_DEVICE_FD_FROM_PATH: --("tun-device-fd-from-path") +takes_value "Tun device file descriptor will be transferred from this unix domain socket path") + ); + } + } + #[cfg(unix)] { app = clap_app!(@app (app) @@ -258,9 +275,7 @@ fn main() { config.outbound_vpn_protect_path = Some(From::from("protect_path")); } - if let Some(local_addr) = matches.value_of("LOCAL_ADDR") { - let local_addr = local_addr.parse::().expect("local bind addr"); - + if matches.value_of("LOCAL_ADDR").is_some() || matches.value_of("PROTOCOL").is_some() { let protocol = match matches.value_of("PROTOCOL") { Some("socks") => ProtocolType::Socks, #[cfg(feature = "local-http")] @@ -271,11 +286,31 @@ fn main() { Some("redir") => ProtocolType::Redir, #[cfg(feature = "local-dns")] Some("dns") => ProtocolType::Dns, + #[cfg(feature = "local-tun")] + Some("tun") => ProtocolType::Tun, Some(p) => panic!("not supported `protocol` \"{}\"", p), None => ProtocolType::Socks, }; - let mut local_config = LocalConfig::new(local_addr, protocol); + let mut local_config = LocalConfig::new(protocol); + match (protocol, matches.value_of("LOCAL_ADDR")) { + #[cfg(feature = "local-tun")] + (ProtocolType::Tun, local_addr_opt) => { + if let Some(local_addr) = local_addr_opt { + local_config.addr = Some(local_addr.parse::().expect("local bind addr")); + } + } + (_, None) => { + ClapError::with_description( + format!("Protocol \"{}\" requires local-addr", protocol.as_str()).as_str(), + ClapErrorKind::ArgumentNotFound, + ) + .exit(); + } + (_, Some(local_addr)) => { + local_config.addr = Some(local_addr.parse::().expect("local bind addr")); + } + } if let Some(udp_bind_addr) = matches.value_of("UDP_BIND_ADDR") { local_config.udp_addr = Some(udp_bind_addr.parse::().expect("udp-bind-addr")); @@ -321,7 +356,7 @@ fn main() { if let Some(dns_relay_addr) = matches.value_of("DNS_LOCAL_ADDR") { let addr = dns_relay_addr.parse::().expect("dns relay address"); - let mut local_dns_config = LocalConfig::new(addr, ProtocolType::Dns); + let mut local_dns_config = LocalConfig::new_with_addr(addr, ProtocolType::Dns); // The `local_dns_addr` and `remote_dns_addr` are for this DNS server (for compatibility) local_dns_config.local_dns_addr = local_config.local_dns_addr.take(); @@ -331,6 +366,21 @@ fn main() { } } + #[cfg(feature = "local-tun")] + { + if let Some(tun_address) = matches.value_of("TUN_INTERFACE_ADDRESS") { + local_config.tun_interface_address = Some(tun_address.parse().expect("tun-interface-address")); + } + if let Some(tun_name) = matches.value_of("TUN_INTERFACE_NAME") { + local_config.tun_interface_name = Some(tun_name.to_owned()); + } + + #[cfg(unix)] + if let Some(fd_path) = matches.value_of("TUN_DEVICE_FD_FROM_PATH") { + local_config.tun_device_fd_from_path = Some(fd_path.into()); + } + } + if matches.is_present("UDP_ONLY") { local_config.mode = Mode::UdpOnly; } @@ -413,6 +463,11 @@ fn main() { config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); } + if let Some(bind_addr) = matches.value_of("OUTBOUND_BIND_ADDR") { + let bind_addr = bind_addr.parse::().expect("outbound-bind-addr"); + config.outbound_bind_addr = Some(bind_addr); + } + // DONE READING options if config.local.is_empty() { diff --git a/bin/ssmanager.rs b/bin/ssmanager.rs index a3657061..15091c96 100644 --- a/bin/ssmanager.rs +++ b/bin/ssmanager.rs @@ -49,7 +49,7 @@ fn main() { the only required fields are \"manager_address\" and \"manager_port\". \ Servers defined will be created when process is started.") - (@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") + (@arg OUTBOUND_BIND_ADDR: -b --("outbound-bind-addr") +takes_value alias("bind-addr") {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") (@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server") (@arg MANAGER_ADDR: --("manager-addr") +takes_value alias("manager-address") {validator::validate_manager_addr} "ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock") @@ -151,9 +151,9 @@ fn main() { None => Config::new(ConfigType::Manager), }; - if let Some(bind_addr) = matches.value_of("BIND_ADDR") { - let bind_addr = bind_addr.parse::().expect("bind-addr"); - config.local_addr = Some(bind_addr); + if let Some(bind_addr) = matches.value_of("OUTBOUND_BIND_ADDR") { + let bind_addr = bind_addr.parse::().expect("outbound-bind-addr"); + config.outbound_bind_addr = Some(bind_addr); } if matches.is_present("TCP_NO_DELAY") { diff --git a/bin/ssserver.rs b/bin/ssserver.rs index 0d0b6aa9..7be2fb12 100644 --- a/bin/ssserver.rs +++ b/bin/ssserver.rs @@ -43,7 +43,7 @@ fn main() { (@arg CONFIG: -c --config +takes_value required_unless("SERVER_ADDR") "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") - (@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") + (@arg OUTBOUND_BIND_ADDR: -b --("outbound-bind-addr") +takes_value alias("bind-addr") {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") (@arg SERVER_ADDR: -s --("server-addr") +takes_value {validator::validate_server_addr} requires[PASSWORD ENCRYPT_METHOD] "Server address") (@arg PASSWORD: -k --password +takes_value requires[SERVER_ADDR] "Server's password") @@ -188,9 +188,9 @@ fn main() { config.server.push(sc); } - if let Some(bind_addr) = matches.value_of("BIND_ADDR") { - let bind_addr = bind_addr.parse::().expect("bind-addr"); - config.local_addr = Some(bind_addr); + if let Some(bind_addr) = matches.value_of("OUTBOUND_BIND_ADDR") { + let bind_addr = bind_addr.parse::().expect("outbound-bind-addr"); + config.outbound_bind_addr = Some(bind_addr); } if matches.is_present("TCP_NO_DELAY") { diff --git a/build/build-host-release b/build/build-host-release index e956c328..4e724663 100755 --- a/build/build-host-release +++ b/build/build-host-release @@ -8,7 +8,7 @@ while getopts "t:f:" opt; do BUILD_TARGET=$OPTARG ;; f) - BUILD_FEATURES=($OPTARG) + BUILD_FEATURES+=($OPTARG) ;; ?) echo "Usage: $(basename $0) [-t ] [-f ]" diff --git a/build/build-release b/build/build-release index 05e01b9e..4fb16831 100755 --- a/build/build-release +++ b/build/build-release @@ -46,7 +46,7 @@ function build() { TARGET_FEATURES="${features[@]}" if [[ "$TARGET" == *"-linux-"* || "$TARGET" == *"-darwin" ]]; then - TARGET_FEATURES+=" local-redir" + TARGET_FEATURES+=" local-redir local-tun" fi if [[ "${TARGET_FEATURES}" != "" ]]; then diff --git a/crates/shadowsocks-service/Cargo.toml b/crates/shadowsocks-service/Cargo.toml index f215e93d..50723257 100644 --- a/crates/shadowsocks-service/Cargo.toml +++ b/crates/shadowsocks-service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-service" -version = "1.11.3" +version = "1.12.0" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" @@ -55,6 +55,8 @@ local-redir = ["local", "mio"] local-tunnel = ["local"] # Enable socks4 protocol for sslocal local-socks4 = ["local"] +# Enable Tun interface protocol for sslocal +local-tun = ["local", "etherparse", "tun", "rand"] # Enable Stream Cipher Protocol # WARN: Stream Cipher Protocol is proved to be insecured @@ -105,10 +107,13 @@ ipnet = "2.3" iprange = "0.6" regex = "1.4" +tun = { version = "0.5", optional = true } +etherparse = { version = "0.9", optional = true } + serde = { version = "1.0", features = ["derive"] } json5 = "0.3" -shadowsocks = { version = "1.11.3", path = "../shadowsocks" } +shadowsocks = { version = "1.12.0", path = "../shadowsocks" } # 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] diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index e1818718..7bfb733a 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -58,6 +58,8 @@ use std::{ }; use cfg_if::cfg_if; +#[cfg(feature = "local-tun")] +use ipnet::IpNet; use serde::{Deserialize, Serialize}; #[cfg(any(feature = "local-tunnel", feature = "local-dns"))] use shadowsocks::relay::socks5::Address; @@ -136,8 +138,10 @@ struct SSConfig { #[derive(Serialize, Deserialize, Debug, Default)] struct SSLocalExtConfig { + #[serde(skip_serializing_if = "Option::is_none")] local_address: Option, - local_port: u16, + #[serde(skip_serializing_if = "Option::is_none")] + local_port: Option, #[serde(skip_serializing_if = "Option::is_none")] disabled: Option, @@ -188,6 +192,14 @@ struct SSLocalExtConfig { #[cfg(feature = "local-tunnel")] #[serde(skip_serializing_if = "Option::is_none")] forward_port: Option, + + /// Tun + #[cfg(feature = "local-tun")] + #[serde(skip_serializing_if = "Option::is_none")] + tun_interface_name: Option, + #[cfg(feature = "local-tun")] + #[serde(skip_serializing_if = "Option::is_none")] + tun_interface_address: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -552,6 +564,8 @@ pub enum ProtocolType { Redir, #[cfg(feature = "local-dns")] Dns, + #[cfg(feature = "local-tun")] + Tun, } impl Default for ProtocolType { @@ -573,6 +587,8 @@ impl ProtocolType { ProtocolType::Redir => "redir", #[cfg(feature = "local-dns")] ProtocolType::Dns => "dns", + #[cfg(feature = "local-tun")] + ProtocolType::Tun => "tun", } } @@ -588,6 +604,8 @@ impl ProtocolType { "redir", #[cfg(feature = "local-dns")] "dns", + #[cfg(feature = "local-tun")] + "tun", ] } } @@ -610,6 +628,8 @@ impl FromStr for ProtocolType { "redir" => Ok(ProtocolType::Redir), #[cfg(feature = "local-dns")] "dns" => Ok(ProtocolType::Dns), + #[cfg(feature = "local-tun")] + "tun" => Ok(ProtocolType::Tun), _ => Err(ProtocolTypeError), } } @@ -618,7 +638,9 @@ impl FromStr for ProtocolType { /// Local server configuration #[derive(Clone, Debug)] pub struct LocalConfig { - pub addr: ServerAddr, + /// Listen address for local servers + pub addr: Option, + pub protocol: ProtocolType, /// Mode @@ -651,13 +673,30 @@ pub struct LocalConfig { /// Sending DNS query through proxy to this address #[cfg(feature = "local-dns")] pub remote_dns_addr: Option
, + + /// Tun interface's name + /// + /// Linux: eth0, eth1, ... + /// macOS: utun0, utun1, ... + #[cfg(feature = "local-tun")] + pub tun_interface_name: Option, + /// Tun interface's address and netmask + #[cfg(feature = "local-tun")] + pub tun_interface_address: Option, + /// Tun interface's file descriptor + #[cfg(all(feature = "local-tun", unix))] + pub tun_device_fd: Option, + /// Tun interface's file descriptor read from this Unix Domain Socket + #[cfg(all(feature = "local-tun", unix))] + pub tun_device_fd_from_path: Option, } impl LocalConfig { /// Create a new `LocalConfig` - pub fn new(addr: ServerAddr, protocol: ProtocolType) -> LocalConfig { + pub fn new(protocol: ProtocolType) -> LocalConfig { LocalConfig { - addr, + addr: None, + protocol, mode: Mode::TcpOnly, @@ -675,10 +714,38 @@ impl LocalConfig { local_dns_addr: None, #[cfg(feature = "local-dns")] remote_dns_addr: None, + + #[cfg(feature = "local-tun")] + tun_interface_name: None, + #[cfg(feature = "local-tun")] + tun_interface_address: None, + #[cfg(all(feature = "local-tun", unix))] + tun_device_fd: None, + #[cfg(all(feature = "local-tun", unix))] + tun_device_fd_from_path: None, } } + /// Create a new `LocalConfig` with listen address + pub fn new_with_addr(addr: ServerAddr, protocol: ProtocolType) -> LocalConfig { + let mut config = LocalConfig::new(protocol); + config.addr = Some(addr); + config + } + fn check_integrity(&self) -> Result<(), Error> { + match self.protocol { + #[cfg(feature = "local-tun")] + ProtocolType::Tun => {} + + _ => { + if self.addr.is_none() { + let err = Error::new(ErrorKind::MissingField, "missing `addr` in configuration", None); + return Err(err); + } + } + } + match self.protocol { #[cfg(feature = "local-dns")] ProtocolType::Dns => { @@ -750,8 +817,6 @@ impl Default for DnsConfig { pub struct Config { /// Remote ShadowSocks server configurations pub server: Vec, - /// Local server's bind address, or ShadowSocks server's outbound address - pub local_addr: Option, /// Local server configuration pub local: Vec, @@ -791,6 +856,8 @@ pub struct Config { /// Set `SO_BINDTODEVICE` socket option for outbound sockets #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] pub outbound_bind_interface: Option, + /// Outbound sockets will `bind` to this address + pub outbound_bind_addr: Option, /// Path to protect callback unix address, only for Android #[cfg(target_os = "android")] pub outbound_vpn_protect_path: Option, @@ -887,7 +954,6 @@ impl Config { pub fn new(config_type: ConfigType) -> Config { Config { server: Vec::new(), - local_addr: None, local: Vec::new(), dns: DnsConfig::default(), @@ -904,6 +970,7 @@ impl Config { outbound_fwmark: None, #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] outbound_bind_interface: None, + outbound_bind_addr: None, #[cfg(target_os = "android")] outbound_vpn_protect_path: None, @@ -992,7 +1059,8 @@ impl Config { get_local_address(config.local_address, local_port, config.ipv6_first.unwrap_or(false)); // shadowsocks uses SOCKS5 by default - let mut local_config = LocalConfig::new(local_addr, ProtocolType::Socks); + let mut local_config = LocalConfig::new(ProtocolType::Socks); + local_config.addr = Some(local_addr); local_config.mode = global_mode; local_config.protocol = match config.protocol { None => ProtocolType::Socks, @@ -1019,17 +1087,6 @@ impl Config { continue; } - if local.local_port == 0 { - let err = Error::new(ErrorKind::Malformed, "`local_port` cannot be 0", None); - return Err(err); - } - - let local_addr = get_local_address( - local.local_address, - local.local_port, - config.ipv6_first.unwrap_or(false), - ); - let protocol = match local.protocol { None => ProtocolType::Socks, Some(p) => match p.parse::() { @@ -1045,7 +1102,21 @@ impl Config { }, }; - let mut local_config = LocalConfig::new(local_addr, protocol); + let mut local_config = LocalConfig::new(protocol); + + if let Some(local_port) = local.local_port { + if local_port == 0 { + let err = Error::new(ErrorKind::Malformed, "`local_port` cannot be 0", None); + return Err(err); + } + + let local_addr = + get_local_address(local.local_address, local_port, config.ipv6_first.unwrap_or(false)); + local_config.addr = Some(local_addr); + } else if local.local_address.is_some() { + let err = Error::new(ErrorKind::Malformed, "missing `local_port`", None); + return Err(err); + } if let Some(local_udp_port) = local.local_udp_port { if local_udp_port == 0 { @@ -1145,24 +1216,31 @@ impl Config { }); } + #[cfg(feature = "local-tun")] + if let Some(tun_interface_address) = local.tun_interface_address { + match tun_interface_address.parse::() { + Ok(addr) => local_config.tun_interface_address = Some(addr), + Err(..) => { + let err = Error::new(ErrorKind::Malformed, "`tun_interface_address` invalid", None); + return Err(err); + } + } + } + + #[cfg(feature = "local-tun")] + if let Some(tun_interface_name) = local.tun_interface_name { + local_config.tun_interface_name = Some(tun_interface_name); + } + nconfig.local.push(local_config); } } } ConfigType::Server | ConfigType::Manager => { + // NOTE: IGNORED. // servers only uses `local_address` for binding outbound interfaces - - if let Some(local_address) = config.local_address { - match local_address.parse::() { - Ok(ip) => { - nconfig.local_addr = Some(ip); - } - Err(..) => { - let err = Error::new(ErrorKind::Malformed, "`local_address` invalid", None); - return Err(err); - } - } - } + // + // This behavior causes lots of confusion. use outbound_bind_addr instead } } @@ -1674,22 +1752,20 @@ impl fmt::Display for Config { let mut jconf = SSConfig::default(); - if let Some(ref client) = self.local_addr { - jconf.local_address = Some(client.to_string()); - } - // Locals if !self.local.is_empty() { if self.local.len() == 1 && self.local[0].is_basic() { let local = &self.local[0]; - jconf.local_address = Some(match local.addr { - ServerAddr::SocketAddr(ref sa) => sa.ip().to_string(), - ServerAddr::DomainName(ref dm, ..) => dm.to_string(), - }); - jconf.local_port = Some(match local.addr { - ServerAddr::SocketAddr(ref sa) => sa.port(), - ServerAddr::DomainName(.., port) => port, - }); + if let Some(ref a) = local.addr { + jconf.local_address = Some(match a { + ServerAddr::SocketAddr(ref sa) => sa.ip().to_string(), + ServerAddr::DomainName(ref dm, ..) => dm.to_string(), + }); + jconf.local_port = Some(match a { + ServerAddr::SocketAddr(ref sa) => sa.port(), + ServerAddr::DomainName(.., port) => *port, + }); + } if local.protocol != ProtocolType::Socks { jconf.protocol = Some(local.protocol.as_str().to_owned()); } @@ -1697,14 +1773,14 @@ impl fmt::Display for Config { let mut jlocals = Vec::with_capacity(self.local.len()); for local in &self.local { let jlocal = SSLocalExtConfig { - local_address: Some(match local.addr { + local_address: local.addr.as_ref().map(|a| match a { ServerAddr::SocketAddr(ref sa) => sa.ip().to_string(), ServerAddr::DomainName(ref dm, ..) => dm.to_string(), }), - local_port: match local.addr { + local_port: local.addr.as_ref().map(|a| match a { ServerAddr::SocketAddr(ref sa) => sa.port(), - ServerAddr::DomainName(.., port) => port, - }, + ServerAddr::DomainName(.., port) => *port, + }), disabled: None, local_udp_address: local.udp_addr.as_ref().map(|udp_addr| match udp_addr { ServerAddr::SocketAddr(sa) => sa.ip().to_string(), @@ -1784,6 +1860,10 @@ impl fmt::Display for Config { Address::DomainNameAddress(.., port) => Some(*port), }, }, + #[cfg(feature = "local-tun")] + tun_interface_name: local.tun_interface_name.clone(), + #[cfg(feature = "local-tun")] + tun_interface_address: local.tun_interface_address.as_ref().map(ToString::to_string), }; jlocals.push(jlocal); } diff --git a/crates/shadowsocks-service/src/local/mod.rs b/crates/shadowsocks-service/src/local/mod.rs index 8ce844dc..ffe6e953 100644 --- a/crates/shadowsocks-service/src/local/mod.rs +++ b/crates/shadowsocks-service/src/local/mod.rs @@ -2,7 +2,11 @@ #[cfg(feature = "local-flow-stat")] use std::path::PathBuf; -use std::{io, sync::Arc, time::Duration}; +use std::{ + io::{self, ErrorKind}, + sync::Arc, + time::Duration, +}; use futures::{ future, @@ -38,6 +42,8 @@ pub mod net; #[cfg(feature = "local-redir")] pub mod redir; pub mod socks; +#[cfg(feature = "local-tun")] +pub mod tun; #[cfg(feature = "local-tunnel")] pub mod tunnel; pub mod utils; @@ -83,6 +89,8 @@ pub async fn run(mut config: Config) -> io::Result<()> { #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] bind_interface: config.outbound_bind_interface, + bind_local_addr: config.outbound_bind_addr, + ..Default::default() }; connect_opts.tcp.send_buffer_size = config.outbound_send_buffer_size; @@ -125,6 +133,8 @@ pub async fn run(mut config: Config) -> io::Result<()> { ProtocolType::Redir => local_config.mode.enable_tcp(), #[cfg(feature = "local-dns")] ProtocolType::Dns => local_config.mode.enable_tcp(), + #[cfg(feature = "local-tun")] + ProtocolType::Tun => true, }); if enable_tcp { @@ -202,12 +212,16 @@ pub async fn run(mut config: Config) -> io::Result<()> { for local_config in config.local { let balancer = balancer.clone(); - let client_addr = local_config.addr; match local_config.protocol { ProtocolType::Socks => { use self::socks::Socks; + let client_addr = match local_config.addr { + Some(a) => a, + None => return Err(io::Error::new(ErrorKind::Other, "socks requires local address")), + }; + let mut server = Socks::with_context(context.clone()); server.set_mode(local_config.mode); @@ -227,6 +241,11 @@ pub async fn run(mut config: Config) -> io::Result<()> { ProtocolType::Tunnel => { use self::tunnel::Tunnel; + let client_addr = match local_config.addr { + Some(a) => a, + None => return Err(io::Error::new(ErrorKind::Other, "tunnel requires local address")), + }; + let forward_addr = local_config.forward_addr.expect("tunnel requires forward address"); let mut server = Tunnel::with_context(context.clone(), forward_addr.clone()); @@ -246,6 +265,11 @@ pub async fn run(mut config: Config) -> io::Result<()> { ProtocolType::Http => { use self::http::Http; + let client_addr = match local_config.addr { + Some(a) => a, + None => return Err(io::Error::new(ErrorKind::Other, "http requires local address")), + }; + let server = Http::with_context(context.clone()); vfut.push(async move { server.run(&client_addr, balancer).await }.boxed()); } @@ -253,6 +277,11 @@ pub async fn run(mut config: Config) -> io::Result<()> { ProtocolType::Redir => { use self::redir::Redir; + let client_addr = match local_config.addr { + Some(a) => a, + None => return Err(io::Error::new(ErrorKind::Other, "redir requires local address")), + }; + let mut server = Redir::with_context(context.clone()); if let Some(c) = config.udp_max_associations { server.set_udp_capacity(c); @@ -271,6 +300,11 @@ pub async fn run(mut config: Config) -> io::Result<()> { ProtocolType::Dns => { use self::dns::Dns; + let client_addr = match local_config.addr { + Some(a) => a, + None => return Err(io::Error::new(ErrorKind::Other, "dns requires local address")), + }; + let mut server = { let local_addr = local_config.local_dns_addr.expect("missing local_dns_addr"); let remote_addr = local_config.remote_dns_addr.expect("missing remote_dns_addr"); @@ -281,6 +315,78 @@ pub async fn run(mut config: Config) -> io::Result<()> { vfut.push(async move { server.run(&client_addr, balancer).await }.boxed()); } + #[cfg(feature = "local-tun")] + ProtocolType::Tun => { + use log::info; + use shadowsocks::net::UnixListener; + + use self::tun::TunBuilder; + + let mut builder = TunBuilder::new(context.clone(), balancer); + if let Some(address) = local_config.tun_interface_address { + builder = builder.address(address); + } + if let Some(name) = local_config.tun_interface_name { + builder = builder.name(&name); + } + if let Some(c) = config.udp_max_associations { + builder = builder.udp_capacity(c); + } + if let Some(d) = config.udp_timeout { + builder = builder.udp_expiry_duration(d); + } + #[cfg(unix)] + if let Some(fd) = local_config.tun_device_fd { + builder = builder.file_descriptor(fd); + } else if let Some(ref fd_path) = local_config.tun_device_fd_from_path { + use std::fs; + + let _ = fs::remove_file(fd_path); + + let listener = match UnixListener::bind(fd_path) { + Ok(l) => l, + Err(err) => { + error!("failed to bind uds path \"{}\", error: {}", fd_path.display(), err); + return Err(err); + } + }; + + info!("waiting tun's file descriptor from {}", fd_path.display()); + + loop { + let (mut stream, peer_addr) = listener.accept().await?; + trace!("accepted {:?} for receiving tun file descriptor", peer_addr); + + let mut buffer = [0u8; 1024]; + let mut fd_buffer = [0]; + + match stream.recv_with_fd(&mut buffer, &mut fd_buffer).await { + Ok((n, fd_size)) => { + if fd_size == 0 { + error!( + "client {:?} didn't send file descriptors with buffer.size {} bytes", + peer_addr, n + ); + continue; + } + + info!("got file descriptor {} for tun from {:?}", fd_buffer[0], peer_addr); + + builder = builder.file_descriptor(fd_buffer[0]); + break; + } + Err(err) => { + error!( + "failed to receive file descriptors from {:?}, error: {}", + peer_addr, err + ); + } + } + } + } + let server = builder.build().await?; + vfut.push(async move { server.run().await }.boxed()); + } } } diff --git a/crates/shadowsocks-service/src/local/redir/mod.rs b/crates/shadowsocks-service/src/local/redir/mod.rs index 451f3796..3374c4df 100644 --- a/crates/shadowsocks-service/src/local/redir/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/mod.rs @@ -1,7 +1,5 @@ //! Shadowsocks Local Transparent Proxy -use std::net::{Ipv4Addr, Ipv6Addr}; - pub use self::server::Redir; mod redir_ext; @@ -9,13 +7,3 @@ mod server; mod sys; mod tcprelay; mod udprelay; - -/// Helper function for converting IPv4 mapped IPv6 address -/// -/// This is the same as `Ipv6Addr::to_ipv4_mapped`, but it is still unstable in the current libstd -fn to_ipv4_mapped(ipv6: &Ipv6Addr) -> Option { - match ipv6.octets() { - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => Some(Ipv4Addr::new(a, b, c, d)), - _ => None, - } -} diff --git a/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs b/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs index 2272bc0b..1192c298 100644 --- a/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs @@ -20,11 +20,8 @@ use crate::{ context::ServiceContext, loadbalancing::PingBalancer, net::AutoProxyClientStream, - redir::{ - redir_ext::{TcpListenerRedirExt, TcpStreamRedirExt}, - to_ipv4_mapped, - }, - utils::establish_tcp_tunnel, + redir::redir_ext::{TcpListenerRedirExt, TcpStreamRedirExt}, + utils::{establish_tcp_tunnel, to_ipv4_mapped}, }, }; diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs b/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs index d0a27f7f..4e7bcdba 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/mod.rs @@ -24,10 +24,8 @@ use crate::{ context::ServiceContext, loadbalancing::PingBalancer, net::{UdpAssociationManager, UdpInboundWrite}, - redir::{ - redir_ext::{RedirSocketOpts, UdpSocketRedirExt}, - to_ipv4_mapped, - }, + redir::redir_ext::{RedirSocketOpts, UdpSocketRedirExt}, + utils::to_ipv4_mapped, }, }; diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs index a87720b8..e1dd37f1 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs @@ -119,15 +119,17 @@ fn set_bindany(level: libc::c_int, socket: &Socket) -> io::Result<()> { _ => unreachable!("level can only be IPPROTO_IP or IPPROTO_IPV6"), }; - let ret = libc::setsockopt( - fd, - level, - opt, - &enable as *const _ as *const _, - mem::size_of_val(&enable) as libc::socklen_t, - ); - if ret != 0 { - return Err(Error::last_os_error()); + unsafe { + let ret = libc::setsockopt( + fd, + level, + opt, + &enable as *const _ as *const _, + mem::size_of_val(&enable) as libc::socklen_t, + ); + if ret != 0 { + return Err(Error::last_os_error()); + } } Ok(()) @@ -140,15 +142,17 @@ fn set_bindany(_level: libc::c_int, socket: &Socket) -> io::Result<()> { let enable: libc::c_int = 1; // https://man.openbsd.org/getsockopt.2 - let ret = libc::setsockopt( - fd, - libc::SOL_SOCKET, - libc::SO_BINDANY, - &enable as *const _ as *const _, - mem::size_of_val(&enable) as libc::socklen_t, - ); - if ret != 0 { - return Err(Error::last_os_error()); + unsafe { + let ret = libc::setsockopt( + fd, + libc::SOL_SOCKET, + libc::SO_BINDANY, + &enable as *const _ as *const _, + mem::size_of_val(&enable) as libc::socklen_t, + ); + if ret != 0 { + return Err(Error::last_os_error()); + } } Ok(()) diff --git a/crates/shadowsocks-service/src/local/tun/mod.rs b/crates/shadowsocks-service/src/local/tun/mod.rs new file mode 100644 index 00000000..e2587ebb --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/mod.rs @@ -0,0 +1,296 @@ +//! Shadowsocks Local server serving on a Tun interface + +#[cfg(unix)] +use std::os::unix::io::RawFd; +use std::{ + io::{self, Cursor, ErrorKind}, + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, + sync::Arc, + time::Duration, +}; + +use byte_string::ByteStr; +use bytes::BytesMut; +use etherparse::{IpHeader, PacketHeaders, ReadError, TransportHeader}; +use ipnet::{IpNet, Ipv4Net}; +use log::{error, info, trace, warn}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + sync::mpsc, +}; +use tun::{Configuration as TunConfiguration, Device, Layer}; + +use crate::local::{context::ServiceContext, loadbalancing::PingBalancer}; + +use self::{ + sys::{set_packet_information, set_route_configuration, AsyncDevice, IFF_PI_PREFIX_LEN}, + tcp::TcpTun, + udp::UdpTun, +}; + +mod sys; +mod tcp; +mod udp; + +pub struct TunBuilder { + context: Arc, + balancer: PingBalancer, + tun_config: TunConfiguration, + udp_expiry_duration: Option, + udp_capacity: Option, +} + +impl TunBuilder { + pub fn new(context: Arc, balancer: PingBalancer) -> TunBuilder { + TunBuilder { + context, + balancer, + tun_config: TunConfiguration::default(), + udp_expiry_duration: None, + udp_capacity: None, + } + } + + pub fn address(mut self, addr: IpNet) -> TunBuilder { + self.tun_config.address(addr.addr()).netmask(addr.netmask()); + self + } + + pub fn name(mut self, name: &str) -> TunBuilder { + self.tun_config.name(name); + self + } + + #[cfg(unix)] + pub fn file_descriptor(mut self, fd: RawFd) -> TunBuilder { + self.tun_config.raw_fd(fd); + self + } + + pub fn udp_expiry_duration(mut self, udp_expiry_duration: Duration) -> TunBuilder { + self.udp_expiry_duration = Some(udp_expiry_duration); + self + } + + pub fn udp_capacity(mut self, udp_capacity: usize) -> TunBuilder { + self.udp_capacity = Some(udp_capacity); + self + } + + pub async fn build(mut self) -> io::Result { + self.tun_config.layer(Layer::L3).up(); + + #[cfg(any(target_os = "linux"))] + self.tun_config.platform(|tun_config| { + // IFF_NO_PI preventing excessive buffer reallocating + tun_config.packet_information(false); + }); + + let device = AsyncDevice::create(&self.tun_config)?; + + let tun_address = match device.get_ref().address() { + Ok(t) => t, + Err(err) => { + error!( + "tun device doesn't have address, error: {}, set it by tun_interface_address", + err + ); + return Err(io::Error::new(ErrorKind::Other, err)); + } + }; + + let tun_netmask = match device.get_ref().netmask() { + Ok(m) => m, + Err(err) => { + error!( + "tun device doesn't have netmask, error: {}, set it by tun_interface_address", + err + ); + return Err(io::Error::new(ErrorKind::Other, err)); + } + }; + + let tun_name = device.get_ref().name(); + + trace!("tun {}, address: {}, netmask: {}", tun_name, tun_address, tun_netmask); + + if let Err(err) = set_route_configuration(device.get_ref()).await { + warn!( + "failed to set system route for {}, consider set it manually, error: {}", + tun_name, err + ); + } + + let tun_netmask_u32: u32 = tun_netmask.into(); + + let tun_network = Ipv4Net::new(tun_address, tun_netmask_u32.leading_ones() as u8).expect("Ipv4Net::new"); + + let (tun_tx, tun_rx) = mpsc::channel(64); + + Ok(Tun { + device, + tun_rx, + tcp: TcpTun::new(self.context.clone(), tun_network.into(), self.balancer.clone()).await?, + udp: UdpTun::new( + self.context, + tun_tx, + self.balancer, + self.udp_expiry_duration, + self.udp_capacity, + ), + }) + } +} + +pub struct Tun { + device: AsyncDevice, + tun_rx: mpsc::Receiver, + tcp: TcpTun, + udp: UdpTun, +} + +impl Tun { + pub async fn run(mut self) -> io::Result<()> { + let mtu = self.device.get_ref().mtu().expect("mtu"); + assert!(mtu > 0 && mtu as usize > IFF_PI_PREFIX_LEN); + + info!( + "shadowsocks tun device {}, address {}, netmask {}, mtu {}", + self.device.get_ref().name(), + self.device.get_ref().address().expect("address"), + self.device.get_ref().netmask().expect("netmask"), + mtu, + ); + + let mut packet_buffer = vec![0u8; mtu as usize + IFF_PI_PREFIX_LEN].into_boxed_slice(); + + loop { + tokio::select! { + // tun device + n = self.device.read(&mut packet_buffer) => { + let n = n?; + + if n <= IFF_PI_PREFIX_LEN { + error!( + "[TUN] packet too short, packet: {:?}", + ByteStr::new(&packet_buffer[..n]) + ); + continue; + } + + let packet = &mut packet_buffer[IFF_PI_PREFIX_LEN..n]; + trace!("[TUN] received IP packet {:?}", ByteStr::new(packet)); + + if self.handle_packet(packet).await? { + self.device.write_all(&packet_buffer[..n]).await?; + } + } + + // channel sent back + maybe_packet = self.tun_rx.recv() => { + let mut packet = maybe_packet.expect("tun channel closed"); + match set_packet_information(&mut packet) { + Err(err) => { + error!("failed to set packet information, error: {}, {:?}", err, ByteStr::new(&packet)); + } + Ok(..) => { + self.device.write_all(&packet).await?; + } + } + } + } + } + } + + async fn handle_packet(&mut self, packet: &mut [u8]) -> io::Result { + let mut ph = match PacketHeaders::from_ip_slice(packet) { + Ok(ph) => ph, + Err(ReadError::IoError(err)) => return Err(err), + Err(err) => { + error!("invalid IP packet, error: {:?}, {:?}", err, ByteStr::new(packet)); + return Err(io::Error::new(ErrorKind::Other, err)); + } + }; + + let payload_len = ph.payload.len(); + + let mut ip_header = match ph.ip { + Some(ref mut i) => i, + None => { + error!("unrecognized ethernet packet {:?}", ph); + return Err(io::Error::new(ErrorKind::Other, "unrecognized ethernet packet")); + } + }; + + let (src_ip, dst_ip) = match *ip_header { + IpHeader::Version4(ref v4) => (Ipv4Addr::from(v4.source).into(), Ipv4Addr::from(v4.destination).into()), + IpHeader::Version6(ref v6) => (Ipv6Addr::from(v6.source).into(), Ipv6Addr::from(v6.destination).into()), + }; + + match ph.transport { + Some(TransportHeader::Tcp(ref mut tcp_header)) => { + let src_addr = SocketAddr::new(src_ip, tcp_header.source_port); + let dst_addr = SocketAddr::new(dst_ip, tcp_header.destination_port); + + let (mod_src_addr, mod_dst_addr) = match self.tcp.handle_packet(src_addr, dst_addr, &tcp_header).await { + Ok(Some(a)) => a, + Ok(None) => return Ok(false), + Err(err) => { + error!("handle TCP/IP packet failed, error: {}", err); + return Ok(false); + } + }; + + // Replaces IP_HEADER, TRANSPORT_HEADER directly into packet + match (mod_src_addr, &mut ip_header) { + (SocketAddr::V4(v4addr), IpHeader::Version4(v4ip)) => v4ip.source = v4addr.ip().octets(), + (SocketAddr::V6(v6addr), IpHeader::Version6(v6ip)) => v6ip.source = v6addr.ip().octets(), + _ => unreachable!("modified saddr not match"), + } + tcp_header.source_port = mod_src_addr.port(); + match (mod_dst_addr, &mut ip_header) { + (SocketAddr::V4(v4addr), IpHeader::Version4(v4ip)) => v4ip.destination = v4addr.ip().octets(), + (SocketAddr::V6(v6addr), IpHeader::Version6(v6ip)) => v6ip.destination = v6addr.ip().octets(), + _ => unreachable!("modified daddr not match"), + } + tcp_header.destination_port = mod_dst_addr.port(); + match ip_header { + IpHeader::Version4(v4) => { + tcp_header.checksum = tcp_header + .calc_checksum_ipv4(v4, ph.payload) + .expect("calc_checksum_ipv4") + } + IpHeader::Version6(v6) => { + tcp_header.checksum = tcp_header + .calc_checksum_ipv6(v6, ph.payload) + .expect("calc_checksum_ipv6") + } + } + + let (headers, _) = packet.split_at_mut(packet.len() - payload_len); + let mut cursor = Cursor::new(headers); + + ip_header.write(&mut cursor).expect("ip_header.write"); + tcp_header.write(&mut cursor).expect("tcp_header.write"); + + Ok(true) + } + Some(TransportHeader::Udp(ref udp_header)) => { + // UDP proxies directly + + let src_addr = SocketAddr::new(src_ip, udp_header.source_port); + let dst_addr = SocketAddr::new(dst_ip, udp_header.destination_port); + + if let Err(err) = self.udp.handle_packet(src_addr, dst_addr, ph.payload).await { + error!("handle UDP/IP packet failed, error: {}", err); + } + + Ok(false) + } + None => { + trace!("no transport layer in ethernet packet {:?}", ph); + Ok(false) + } + } + } +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/mod.rs b/crates/shadowsocks-service/src/local/tun/sys/mod.rs new file mode 100644 index 00000000..0f9a1a2a --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/mod.rs @@ -0,0 +1,11 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(unix)] { + mod unix; + pub use self::unix::*; + } else if #[cfg(windows)] { + mod windows; + pub use self::windows::*; + } +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/android.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/android.rs new file mode 100644 index 00000000..e0576f3b --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/android.rs @@ -0,0 +1,44 @@ +use std::io::{self, ErrorKind}; + +use bytes::{BufMut, BytesMut}; +use tun::platform::Device as TunDevice; + +/// Packet Information length in bytes +pub const IFF_PI_PREFIX_LEN: usize = 4; + +/// Prepending Packet Information +/// +/// ``` +/// +--------+--------+--------+--------+ +/// | Flags (0) | Protocol | +/// +--------+--------+--------+--------+ +/// ``` +pub fn set_packet_information(packet: &mut BytesMut) -> io::Result<()> { + if packet.is_empty() { + return Err(io::Error::new(ErrorKind::InvalidInput, "empty packet")); + } + + // FIXME: Bad Performance because of new allocation and memory copies. + let mut full_packet = BytesMut::with_capacity(4 + packet.len()); + + // Flags, always 0 + full_packet.put_u16(0); + // Protocol, infer from the original packet + let protocol = match packet[0] >> 4 { + 4 => libc::ETH_P_IP, + 6 => libc::ETH_P_IPV6, + _ => return Err(io::Error::new(ErrorKind::InvalidData, "neither an IPv4 or IPv6 packet")), + }; + full_packet.put_u16(protocol as u16); + + // Append the whole packet + full_packet.put_slice(&packet); + + *packet = full_packet; + Ok(()) +} + +/// Set platform specific route configuration +pub async fn set_route_configuration(_device: &TunDevice) -> io::Result<()> { + Ok(()) +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/apple/macos.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/apple/macos.rs new file mode 100644 index 00000000..c31b9855 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/apple/macos.rs @@ -0,0 +1,171 @@ +use std::{ + ffi::CStr, + io::{self, ErrorKind}, + mem, + ptr, +}; + +use log::{error, trace}; +use tun::{platform::Device as TunDevice, Device}; + +/// These numbers are used by reliable protocols for determining +/// retransmission behavior and are included in the routing structure. +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy)] +struct rt_metrics { + rmx_locks: u32, //< Kernel must leave these values alone + rmx_mtu: u32, //< MTU for this path + rmx_hopcount: u32, //< max hops expected + rmx_expire: i32, //< lifetime for route, e.g. redirect + rmx_recvpipe: u32, //< inbound delay-bandwidth product + rmx_sendpipe: u32, //< outbound delay-bandwidth product + rmx_ssthresh: u32, //< outbound gateway buffer limit + rmx_rtt: u32, //< estimated round trip time + rmx_rttvar: u32, //< estimated rtt variance + rmx_pksent: u32, //< packets sent using this route + rmx_state: u32, //< route state + rmx_filler: [u32; 3], //< will be used for T/TCP later +} + +/// Structures for routing messages. +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy)] +struct rt_msghdr { + rtm_msglen: libc::c_ushort, //< to skip over non-understood messages + rtm_version: libc::c_uchar, //< future binary compatibility + rtm_type: libc::c_uchar, //< message type + rtm_index: libc::c_ushort, //< index for associated ifp + rtm_flags: libc::c_int, //< flags, incl. kern & message, e.g. DONE + rtm_addrs: libc::c_int, //< bitmask identifying sockaddrs in msg + rtm_pid: libc::pid_t, //< identify sender + rtm_seq: libc::c_int, //< for sender to identify action + rtm_errno: libc::c_int, //< why failed + rtm_use: libc::c_int, //< from rtentry + rtm_inits: u32, //< which metrics we are initializing + rtm_rmx: rt_metrics, //< metrics themselves +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy)] +struct rt_msg { + rtm: rt_msghdr, + dst: libc::sockaddr_in, + gateway: libc::sockaddr_dl, + netmask: libc::sockaddr_in, +} + +/// Set platform specific route configuration +pub async fn set_route_configuration(device: &TunDevice) -> io::Result<()> { + let tun_address = match device.address() { + Ok(t) => t, + Err(err) => { + error!("tun device doesn't have address, error: {}", err); + return Err(io::Error::new(ErrorKind::Other, err)); + } + }; + + let tun_netmask = match device.netmask() { + Ok(m) => m, + Err(err) => { + error!("tun device doesn't have netmask, error: {}", err); + return Err(io::Error::new(ErrorKind::Other, err)); + } + }; + + let tun_name = device.name(); + + // routing packets that saddr & daddr are in the subnet of the Tun interface + // + // This is only required for the TCP tunnel, and it is the default behavior on Linux + // + // https://opensource.apple.com/source/network_cmds/network_cmds-307.0.1/route.tproj/route.c.auto.html + + unsafe { + let mut rtmsg: rt_msg = mem::zeroed(); + rtmsg.rtm.rtm_type = libc::RTM_ADD as libc::c_uchar; + rtmsg.rtm.rtm_flags = libc::RTF_UP | libc::RTF_STATIC; + rtmsg.rtm.rtm_version = libc::RTM_VERSION as libc::c_uchar; + rtmsg.rtm.rtm_seq = rand::random(); + rtmsg.rtm.rtm_addrs = libc::RTA_DST | libc::RTA_GATEWAY | libc::RTA_NETMASK; + rtmsg.rtm.rtm_msglen = mem::size_of_val(&rtmsg) as libc::c_ushort; + rtmsg.rtm.rtm_pid = libc::getpid(); + + // Set address as destination addr + { + rtmsg.dst.sin_family = libc::AF_INET as libc::sa_family_t; + rtmsg.dst.sin_addr = libc::in_addr { + s_addr: u32::from_ne_bytes(tun_address.octets()), + }; + rtmsg.dst.sin_len = mem::size_of_val(&rtmsg.dst) as u8; + } + + // Get the interface's link address (sockaddr_dl) + let found_gateway = { + let mut found_ifaddr = false; + + let mut ifap: *mut libc::ifaddrs = ptr::null_mut(); + if libc::getifaddrs(&mut ifap) != 0 { + return Err(io::Error::last_os_error()); + } + + let mut ifa = ifap; + while !ifa.is_null() { + if !(*ifa).ifa_addr.is_null() && (*(*ifa).ifa_addr).sa_family as i32 == libc::AF_LINK { + let ifa_name = CStr::from_ptr((*ifa).ifa_name); + if ifa_name.to_bytes() == tun_name.as_bytes() { + // Found the link_addr of tun interface. + + let sdl: *mut libc::sockaddr_dl = (*ifa).ifa_addr as *mut _; + rtmsg.gateway = *sdl; + + found_ifaddr = true; + break; + } + } + + ifa = (*ifa).ifa_next; + } + libc::freeifaddrs(ifap); + + found_ifaddr + }; + + if !found_gateway { + error!("couldn't get interface \"{}\" AF_LINK address", tun_name); + return Err(io::Error::new( + ErrorKind::Other, + "couldn't get interface AF_LINK address", + )); + } + + // netmask + { + rtmsg.netmask.sin_family = libc::AF_INET as libc::sa_family_t; + rtmsg.netmask.sin_addr = libc::in_addr { + s_addr: u32::from_ne_bytes(tun_netmask.octets()), + }; + rtmsg.netmask.sin_len = mem::size_of_val(&rtmsg.netmask) as u8; + } + + trace!("add route {:?}", rtmsg); + + let fd = libc::socket(libc::PF_ROUTE, libc::SOCK_RAW, 0); + if fd < 0 { + return Err(io::Error::last_os_error()); + } + + let n = libc::write(fd, &mut rtmsg as *mut _ as *mut _, mem::size_of_val(&rtmsg)); + if n < 0 { + let err = io::Error::last_os_error(); + libc::close(fd); + return Err(err); + } + + libc::close(fd); + } + + Ok(()) +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/apple/mod.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/apple/mod.rs new file mode 100644 index 00000000..930533d8 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/apple/mod.rs @@ -0,0 +1,49 @@ +use std::io::{self, ErrorKind}; + +use bytes::{BufMut, BytesMut}; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(target_os = "macos")] { + mod macos; + pub use self::macos::*; + } else { + mod others; + pub use self::others::*; + } +} + +/// Packet Information length in bytes +pub const IFF_PI_PREFIX_LEN: usize = 4; + +/// Prepending Packet Information +/// +/// ``` +/// +--------+--------+--------+--------+ +/// | Flags (0) | Protocol | +/// +--------+--------+--------+--------+ +/// ``` +pub fn set_packet_information(packet: &mut BytesMut) -> io::Result<()> { + if packet.is_empty() { + return Err(io::Error::new(ErrorKind::InvalidInput, "empty packet")); + } + + // FIXME: Bad Performance because of new allocation and memory copies. + let mut full_packet = BytesMut::with_capacity(4 + packet.len()); + + // Flags, always 0 + full_packet.put_u16(0); + // Protocol, infer from the original packet + let protocol = match packet[0] >> 4 { + 4 => libc::PF_INET, + 6 => libc::PF_INET6, + _ => return Err(io::Error::new(ErrorKind::InvalidData, "neither an IPv4 or IPv6 packet")), + }; + full_packet.put_u16(protocol as u16); + + // Append the whole packet + full_packet.put_slice(&packet); + + *packet = full_packet; + Ok(()) +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/apple/others.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/apple/others.rs new file mode 100644 index 00000000..7c28f6b3 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/apple/others.rs @@ -0,0 +1,8 @@ +use std::io; + +use tun::platform::Device as TunDevice; + +/// Set platform specific route configuration +pub async fn set_route_configuration(_device: &TunDevice) -> io::Result<()> { + Ok(()) +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/bsd.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/bsd.rs new file mode 100644 index 00000000..c65f8fe6 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/bsd.rs @@ -0,0 +1,44 @@ +use std::io::{self, ErrorKind}; + +use bytes::{BufMut, BytesMut}; +use tun::platform::Device as TunDevice; + +/// Packet Information length in bytes +pub const IFF_PI_PREFIX_LEN: usize = 4; + +/// Prepending Packet Information +/// +/// ``` +/// +--------+--------+--------+--------+ +/// | Flags (0) | Protocol | +/// +--------+--------+--------+--------+ +/// ``` +pub fn set_packet_information(packet: &mut BytesMut) -> io::Result<()> { + if packet.is_empty() { + return Err(io::Error::new(ErrorKind::InvalidInput, "empty packet")); + } + + // FIXME: Bad Performance because of new allocation and memory copies. + let mut full_packet = BytesMut::with_capacity(4 + packet.len()); + + // Flags, always 0 + full_packet.put_u16(0); + // Protocol, infer from the original packet + let protocol = match packet[0] >> 4 { + 4 => libc::PF_INET, + 6 => libc::PF_INET6, + _ => return Err(io::Error::new(ErrorKind::InvalidData, "neither an IPv4 or IPv6 packet")), + }; + full_packet.put_u16(protocol as u16); + + // Append the whole packet + full_packet.put_slice(&packet); + + *packet = full_packet; + Ok(()) +} + +/// Set platform specific route configuration +pub async fn set_route_configuration(_device: &TunDevice) -> io::Result<()> { + Ok(()) +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/linux.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/linux.rs new file mode 100644 index 00000000..84f99d56 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/linux.rs @@ -0,0 +1,21 @@ +use std::io; + +use bytes::BytesMut; +use tun::platform::Device as TunDevice; + +/// Packet Information length in bytes +/// +/// Tun device have set `IFF_NO_PI`, so ther is no prefix headers +pub const IFF_PI_PREFIX_LEN: usize = 0; + +/// Prepending Packet Information +/// +/// Tun device have set `IFF_NO_PI`, so there is nothing to prepend on Linux +pub fn set_packet_information(_packet: &mut BytesMut) -> io::Result<()> { + Ok(()) +} + +/// Set platform specific route configuration +pub async fn set_route_configuration(_device: &TunDevice) -> io::Result<()> { + Ok(()) +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/unix/mod.rs b/crates/shadowsocks-service/src/local/tun/sys/unix/mod.rs new file mode 100644 index 00000000..7be35ed2 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/unix/mod.rs @@ -0,0 +1,116 @@ +use std::{ + io::{self, ErrorKind, Read, Write}, + os::unix::io::AsRawFd, + pin::Pin, + task::{Context, Poll}, +}; + +use cfg_if::cfg_if; +use futures::ready; +use log::error; +use tokio::io::{unix::AsyncFd, AsyncRead, AsyncWrite, ReadBuf}; +use tun::{platform::Device, Configuration}; + +cfg_if! { + if #[cfg(any(target_os = "linux"))] { + mod linux; + pub use self::linux::*; + } else if #[cfg(target_vendor = "apple")] { + mod apple; + pub use self::apple::*; + } else if #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] { + mod bsd; + pub use self::bsd::*; + } else if #[cfg(target_os = "android")] { + mod android; + pub use self::android::*; + } +} + +pub struct AsyncDevice { + io: AsyncFd, +} + +impl AsyncDevice { + pub fn create(config: &Configuration) -> io::Result { + let device = match tun::create(config) { + Ok(d) => d, + Err(err) => { + error!("failed to create tun device, error: {:?}", err); + return Err(io::Error::new(ErrorKind::Other, err)); + } + }; + + // Set non-blocking for `AsyncFd` + unsafe { + let fd = device.as_raw_fd(); + let ret = libc::fcntl(fd, libc::F_SETFL, libc::fcntl(fd, libc::F_GETFL) | libc::O_NONBLOCK); + if ret < 0 { + return Err(io::Error::last_os_error()); + } + } + + Ok(AsyncDevice { + io: AsyncFd::new(device)?, + }) + } + + #[inline] + pub fn get_ref(&self) -> &Device { + self.io.get_ref() + } +} + +impl AsyncRead for AsyncDevice { + fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + loop { + let mut guard = ready!(self.io.poll_read_ready_mut(cx))?; + + let rbuf = buf.initialize_unfilled(); + match guard.try_io(|io| io.get_mut().read(rbuf)) { + Ok(Ok(n)) => { + buf.advance(n); + return Ok(()).into(); + } + Ok(Err(err)) => return Err(err).into(), + Err(..) => continue, + } + } + } +} + +impl AsyncWrite for AsyncDevice { + fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + loop { + let mut guard = ready!(self.io.poll_write_ready_mut(cx))?; + + match guard.try_io(|io| io.get_mut().write(buf)) { + Ok(r) => return r.into(), + Err(..) => continue, + } + } + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Ok(()).into() + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Ok(()).into() + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + loop { + let mut guard = ready!(self.io.poll_write_ready_mut(cx))?; + + match guard.try_io(|io| io.get_mut().write_vectored(bufs)) { + Ok(r) => return r.into(), + Err(..) => continue, + } + } + } +} diff --git a/crates/shadowsocks-service/src/local/tun/sys/windows/mod.rs b/crates/shadowsocks-service/src/local/tun/sys/windows/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/sys/windows/mod.rs @@ -0,0 +1 @@ + diff --git a/crates/shadowsocks-service/src/local/tun/tcp.rs b/crates/shadowsocks-service/src/local/tun/tcp.rs new file mode 100644 index 00000000..9630cb82 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/tcp.rs @@ -0,0 +1,272 @@ +use std::{ + io::{self, ErrorKind}, + net::{IpAddr, SocketAddr}, + sync::Arc, + time::Duration, +}; + +use etherparse::TcpHeader; +use ipnet::IpNet; +use log::{debug, error, trace}; +use lru_time_cache::LruCache; +use shadowsocks::{net::TcpListener, relay::socks5::Address}; +use tokio::{net::TcpStream, sync::Mutex, task::JoinHandle, time}; + +use crate::local::{ + context::ServiceContext, + loadbalancing::PingBalancer, + net::AutoProxyClientStream, + utils::{establish_tcp_tunnel, to_ipv4_mapped}, +}; + +struct TcpAddressTranslator { + connections: LruCache, + mapping: LruCache<(SocketAddr, SocketAddr), SocketAddr>, +} + +impl TcpAddressTranslator { + fn new() -> TcpAddressTranslator { + TcpAddressTranslator { + connections: LruCache::with_expiry_duration(Duration::from_secs(24 * 60 * 60)), + mapping: LruCache::with_expiry_duration(Duration::from_secs(24 * 60 * 60)), + } + } +} + +pub struct TcpTun { + tcp_daddr: SocketAddr, + free_addrs: Vec, + translator: Arc>, + abortable: JoinHandle>, +} + +impl Drop for TcpTun { + fn drop(&mut self) { + self.abortable.abort(); + } +} + +impl TcpTun { + pub async fn new(context: Arc, tun_network: IpNet, balancer: PingBalancer) -> io::Result { + let mut hosts = tun_network.hosts(); + let tcp_daddr = match hosts.next() { + Some(d) => d, + None => return Err(io::Error::new(ErrorKind::Other, "tun network doesn't have any hosts")), + }; + + // Take up to 10 IPs as saddr for NAT allocating + let free_addrs = hosts.take(10).collect::>(); + if free_addrs.is_empty() { + return Err(io::Error::new( + ErrorKind::InvalidInput, + "tun network doesn't have enough free addresses", + )); + } + + let listener = TcpListener::bind_with_opts(&SocketAddr::new(tcp_daddr, 0), context.accept_opts()).await?; + let tcp_daddr = listener.local_addr()?; + + debug!("tun tcp listener bind {}", tcp_daddr); + + let translator = Arc::new(Mutex::new(TcpAddressTranslator::new())); + + let abortable = { + let translator = translator.clone(); + tokio::spawn(TcpTun::tunnel(context, listener, balancer, translator)) + }; + + Ok(TcpTun { + tcp_daddr, + free_addrs, + translator, + abortable, + }) + } + + pub async fn handle_packet( + &mut self, + src_addr: SocketAddr, + dst_addr: SocketAddr, + tcp_header: &TcpHeader, + ) -> io::Result> { + let TcpAddressTranslator { + ref mut connections, + ref mut mapping, + } = *(self.translator.lock().await); + + let (conn, is_reply) = if tcp_header.syn && !tcp_header.ack { + // 1st SYN, creating a new connection + // Allocate a `saddr` for it + let saddr = loop { + let addr_idx = rand::random::() % self.free_addrs.len(); + let port = rand::random::() % (65535 - 1024) + 1024; + + let addr = SocketAddr::new(self.free_addrs[addr_idx], port); + if !connections.contains_key(&addr) { + trace!("allocated tcp addr {} for {} -> {}", addr, src_addr, dst_addr); + + // Create one in the connection map. + connections.insert( + addr, + TcpConnection { + saddr: src_addr, + daddr: dst_addr, + faked_saddr: addr, + state: TcpState::Established, + }, + ); + + // Record the fake address mapping + mapping.insert((src_addr, dst_addr), addr); + + break addr; + } + }; + + (connections.get_mut(&saddr).unwrap(), false) + } else { + // Find if it is an existed connection, ignore it otherwise + match mapping.get(&(src_addr, dst_addr)) { + Some(saddr) => match connections.get_mut(saddr) { + Some(c) => (c, false), + None => { + debug!("unknown tcp connection {} -> {}", src_addr, dst_addr); + return Ok(None); + } + }, + None => { + // Check if it is a reply packet + match connections.get_mut(&dst_addr) { + Some(c) => (c, true), + None => { + debug!("unknown tcp connection {} -> {}", src_addr, dst_addr); + return Ok(None); + } + } + } + } + }; + + let (trans_saddr, trans_daddr) = if is_reply { + trace!("TCP {} <- {} {:?}", conn.saddr, conn.daddr, tcp_header); + (conn.daddr, conn.saddr) + } else { + trace!("TCP {} -> {} {:?}", conn.saddr, conn.daddr, tcp_header); + (conn.faked_saddr, self.tcp_daddr) + }; + + if tcp_header.rst || (tcp_header.ack && conn.state == TcpState::LastAck) { + // Connection closed. + trace!("tcp connection closed {} -> {}", conn.saddr, conn.daddr); + + mapping.remove(&(src_addr, dst_addr)); + let faked_saddr = conn.faked_saddr; + connections.remove(&faked_saddr); + } else if tcp_header.fin { + match conn.state { + TcpState::Established => conn.state = TcpState::FinWait, + TcpState::FinWait => conn.state = TcpState::LastAck, + _ => {} + } + } + + Ok(Some((trans_saddr, trans_daddr))) + } + + async fn tunnel( + context: Arc, + listener: TcpListener, + balancer: PingBalancer, + translator: Arc>, + ) -> io::Result<()> { + loop { + let (stream, peer_addr) = match listener.accept().await { + Ok(s) => s, + Err(err) => { + error!("accept failed, error: {}", err); + time::sleep(Duration::from_secs(1)).await; + continue; + } + }; + + // Try to translate + let (saddr, daddr) = { + let mut translator = translator.lock().await; + match translator.connections.get(&peer_addr) { + Some(c) => (c.saddr, c.daddr), + None => { + error!("unknown connection from {}", peer_addr); + continue; + } + } + }; + + debug!("establishing tcp tunnel {} -> {}", saddr, daddr); + + let context = context.clone(); + let balancer = balancer.clone(); + tokio::spawn(async move { + if let Err(err) = handle_redir_client(context, balancer, stream, peer_addr, daddr).await { + debug!("TCP redirect client, error: {:?}", err); + } + }); + } + } +} + +/// Established Client Transparent Proxy +/// +/// This method must be called after handshaking with client (for example, socks5 handshaking) +async fn establish_client_tcp_redir<'a>( + context: Arc, + balancer: PingBalancer, + mut stream: TcpStream, + peer_addr: SocketAddr, + addr: &Address, +) -> io::Result<()> { + let server = balancer.best_tcp_server(); + let svr_cfg = server.server_config(); + + let mut remote = AutoProxyClientStream::connect(context, &server, addr).await?; + + establish_tcp_tunnel(svr_cfg, &mut stream, &mut remote, peer_addr, addr).await +} + +async fn handle_redir_client( + context: Arc, + balancer: PingBalancer, + s: TcpStream, + peer_addr: SocketAddr, + mut daddr: SocketAddr, +) -> io::Result<()> { + // Get forward address from socket + // + // Try to convert IPv4 mapped IPv6 address for dual-stack mode. + if let SocketAddr::V6(ref a) = daddr { + if let Some(v4) = to_ipv4_mapped(a.ip()) { + daddr = SocketAddr::new(IpAddr::from(v4), a.port()); + } + } + let target_addr = Address::from(daddr); + establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await +} + +#[derive(Debug, Eq, PartialEq)] +enum TcpState { + /// TCP state `ESTABLISHED` + /// + /// When receiving the first SYN then the state will be set to `ESTABLISHED`. + /// The detailed state like (SYN_SEND, SYN_RCVD) will be handled properly by the `TcpListener`. + Established, + /// When receiving from the first FIN will be transferred from Established + FinWait, + /// When receiving the last ACK of FIN will be transferred from FinWait + LastAck, +} + +struct TcpConnection { + saddr: SocketAddr, + daddr: SocketAddr, + faked_saddr: SocketAddr, + state: TcpState, +} diff --git a/crates/shadowsocks-service/src/local/tun/udp.rs b/crates/shadowsocks-service/src/local/tun/udp.rs new file mode 100644 index 00000000..96361091 --- /dev/null +++ b/crates/shadowsocks-service/src/local/tun/udp.rs @@ -0,0 +1,122 @@ +use std::{ + io::{self, ErrorKind}, + net::{IpAddr, SocketAddr}, + sync::Arc, + time::Duration, +}; + +use async_trait::async_trait; +use bytes::{BufMut, BytesMut}; +use etherparse::PacketBuilder; +use log::trace; +use shadowsocks::relay::socks5::Address; +use tokio::sync::mpsc; + +use crate::local::{ + context::ServiceContext, + loadbalancing::PingBalancer, + net::{UdpAssociationManager, UdpInboundWrite}, + utils::to_ipv4_mapped, +}; + +pub struct UdpTun { + manager: UdpAssociationManager, +} + +impl UdpTun { + pub fn new( + context: Arc, + tun_tx: mpsc::Sender, + balancer: PingBalancer, + time_to_live: Option, + capacity: Option, + ) -> UdpTun { + UdpTun { + manager: UdpAssociationManager::new( + context, + UdpTunInboundWriter::new(tun_tx), + time_to_live, + capacity, + balancer, + ), + } + } + + pub async fn handle_packet( + &mut self, + src_addr: SocketAddr, + dst_addr: SocketAddr, + payload: &[u8], + ) -> io::Result<()> { + trace!("UDP {} -> {} payload.size: {} bytes", src_addr, dst_addr, payload.len()); + self.manager.send_to(src_addr, dst_addr.into(), payload).await + } +} + +#[derive(Clone)] +struct UdpTunInboundWriter { + tun_tx: mpsc::Sender, +} + +impl UdpTunInboundWriter { + fn new(tun_tx: mpsc::Sender) -> UdpTunInboundWriter { + UdpTunInboundWriter { tun_tx } + } +} + +#[async_trait] +impl UdpInboundWrite for UdpTunInboundWriter { + async fn send_to(&self, peer_addr: SocketAddr, remote_addr: &Address, data: &[u8]) -> io::Result<()> { + let addr = match *remote_addr { + Address::SocketAddress(sa) => { + // Try to convert IPv4 mapped IPv6 address if server is running on dual-stack mode + match sa { + SocketAddr::V4(..) => sa, + SocketAddr::V6(ref v6) => match to_ipv4_mapped(v6.ip()) { + Some(v4) => SocketAddr::new(IpAddr::from(v4), v6.port()), + None => sa, + }, + } + } + Address::DomainNameAddress(..) => { + let err = io::Error::new( + ErrorKind::InvalidInput, + "redir destination must not be an domain name address", + ); + return Err(err); + } + }; + + let packet = match (peer_addr, addr) { + (SocketAddr::V4(peer), SocketAddr::V4(remote)) => { + let builder = + PacketBuilder::ipv4(remote.ip().octets(), peer.ip().octets(), 20).udp(remote.port(), peer.port()); + + let packet = BytesMut::with_capacity(builder.size(data.len())); + let mut packet_writer = packet.writer(); + builder.write(&mut packet_writer, data).expect("PacketBuilder::write"); + + packet_writer.into_inner() + } + (SocketAddr::V6(peer), SocketAddr::V6(remote)) => { + let builder = + PacketBuilder::ipv6(remote.ip().octets(), peer.ip().octets(), 20).udp(remote.port(), peer.port()); + + let packet = BytesMut::with_capacity(builder.size(data.len())); + let mut packet_writer = packet.writer(); + builder.write(&mut packet_writer, data).expect("PacketBuilder::write"); + + packet_writer.into_inner() + } + _ => { + return Err(io::Error::new( + ErrorKind::InvalidData, + "source and destination type unmatch", + )); + } + }; + + self.tun_tx.send(packet).await.expect("tun_tx::send"); + Ok(()) + } +} diff --git a/crates/shadowsocks-service/src/local/utils.rs b/crates/shadowsocks-service/src/local/utils.rs index f4473d66..ec671910 100644 --- a/crates/shadowsocks-service/src/local/utils.rs +++ b/crates/shadowsocks-service/src/local/utils.rs @@ -1,6 +1,10 @@ //! Shadowsocks Local Utilities -use std::{io, net::SocketAddr, time::Duration}; +use std::{ + io, + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, + time::Duration, +}; use log::{debug, trace}; use shadowsocks::{ @@ -123,3 +127,14 @@ where Ok(()) } + +/// Helper function for converting IPv4 mapped IPv6 address +/// +/// This is the same as `Ipv6Addr::to_ipv4_mapped`, but it is still unstable in the current libstd +#[allow(unused)] +pub(crate) fn to_ipv4_mapped(ipv6: &Ipv6Addr) -> Option { + match ipv6.octets() { + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a, b, c, d] => Some(Ipv4Addr::new(a, b, c, d)), + _ => None, + } +} diff --git a/crates/shadowsocks-service/src/manager/mod.rs b/crates/shadowsocks-service/src/manager/mod.rs index 566f3afb..ffdb36f7 100644 --- a/crates/shadowsocks-service/src/manager/mod.rs +++ b/crates/shadowsocks-service/src/manager/mod.rs @@ -40,7 +40,7 @@ pub async fn run(config: Config) -> io::Result<()> { #[cfg(target_os = "android")] vpn_protect_path: config.outbound_vpn_protect_path, - bind_local_addr: config.local_addr, + bind_local_addr: config.outbound_bind_addr, #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] bind_interface: config.outbound_bind_interface, diff --git a/crates/shadowsocks-service/src/server/mod.rs b/crates/shadowsocks-service/src/server/mod.rs index a2ba22a3..ec345c6e 100644 --- a/crates/shadowsocks-service/src/server/mod.rs +++ b/crates/shadowsocks-service/src/server/mod.rs @@ -57,7 +57,7 @@ pub async fn run(config: Config) -> io::Result<()> { #[cfg(target_os = "android")] vpn_protect_path: config.outbound_vpn_protect_path, - bind_local_addr: config.local_addr, + bind_local_addr: config.outbound_bind_addr, #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] bind_interface: config.outbound_bind_interface, diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index 4fe92fc1..e04ec10a 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks" -version = "1.11.3" +version = "1.12.0" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/crates/shadowsocks/src/net/mod.rs b/crates/shadowsocks/src/net/mod.rs index 8fc6d776..925825cd 100644 --- a/crates/shadowsocks/src/net/mod.rs +++ b/crates/shadowsocks/src/net/mod.rs @@ -2,6 +2,8 @@ use std::net::SocketAddr; +#[cfg(unix)] +pub use self::sys::uds::{UnixListener, UnixStream}; pub use self::{ option::{AcceptOpts, ConnectOpts}, tcp::{TcpListener, TcpStream}, diff --git a/crates/shadowsocks/src/net/sys/unix/linux/mod.rs b/crates/shadowsocks/src/net/sys/unix/linux/mod.rs index 310265a3..81d2cf47 100644 --- a/crates/shadowsocks/src/net/sys/unix/linux/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/linux/mod.rs @@ -420,7 +420,7 @@ cfg_if! { use std::path::Path; use std::os::unix::io::RawFd; - mod uds; + use super::uds::UnixStream; /// This is a RPC for Android to `protect()` socket for connecting to remote servers /// @@ -430,7 +430,7 @@ cfg_if! { async fn vpn_protect>(protect_path: P, fd: RawFd) -> io::Result<()> { use tokio::io::AsyncReadExt; - let mut stream = self::uds::UnixStream::connect(protect_path).await?; + let mut stream = UnixStream::connect(protect_path).await?; // send fds let dummy: [u8; 1] = [1]; diff --git a/crates/shadowsocks/src/net/sys/unix/mod.rs b/crates/shadowsocks/src/net/sys/unix/mod.rs index 425c82f1..1563ed5e 100644 --- a/crates/shadowsocks/src/net/sys/unix/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/mod.rs @@ -28,6 +28,8 @@ cfg_if! { } } +pub mod uds; + /// Create a `UdpSocket` binded to `addr` pub async fn create_inbound_udp_socket(addr: &SocketAddr) -> io::Result { let set_dual_stack = if let SocketAddr::V6(ref v6) = *addr { diff --git a/crates/shadowsocks/src/net/sys/unix/linux/uds.rs b/crates/shadowsocks/src/net/sys/unix/uds.rs similarity index 53% rename from crates/shadowsocks/src/net/sys/unix/linux/uds.rs rename to crates/shadowsocks/src/net/sys/unix/uds.rs index 460e8b5c..e314efe6 100644 --- a/crates/shadowsocks/src/net/sys/unix/linux/uds.rs +++ b/crates/shadowsocks/src/net/sys/unix/uds.rs @@ -14,7 +14,7 @@ use std::{ }; use futures::{future, ready}; -use mio::net::UnixStream as MioUnixStream; +use mio::net::{SocketAddr, UnixListener as MioUnixListener, UnixStream as MioUnixStream}; use tokio::io::{unix::AsyncFd, AsyncRead, AsyncWrite, ReadBuf}; /// A UnixStream supports transferring FDs between processes @@ -33,6 +33,11 @@ impl UnixStream { Ok(UnixStream { io }) } + /// Create from a mio's `UnixStream` + pub fn from_mio(io: MioUnixStream) -> io::Result { + Ok(UnixStream { io: AsyncFd::new(io)? }) + } + fn poll_send_with_fd(&self, cx: &mut Context, buf: &[u8], fds: &[RawFd]) -> Poll> { loop { let mut ready = ready!(self.io.poll_write_ready(cx))?; @@ -56,6 +61,34 @@ impl UnixStream { future::poll_fn(|cx| self.poll_send_with_fd(cx, buf, fds)).await } + fn poll_recv_with_fd( + &self, + cx: &mut Context, + buf: &mut [u8], + fds: &mut [RawFd], + ) -> Poll> { + loop { + let mut ready = ready!(self.io.poll_write_ready(cx))?; + + let fd = self.io.get_ref().as_raw_fd(); + match recv_with_fd(fd, buf, fds) { + // self.io.poll_write_ready indicates that writable event have been received by tokio, + // so it is not a common case that recvto returns EAGAIN. + // + // Just for double check. If EAGAIN actually returns, clear the readness state. + Err(ref err) if err.kind() == ErrorKind::WouldBlock => { + ready.clear_ready(); + } + x => return Poll::Ready(x), + } + } + } + + /// Recv data with file descriptors + pub async fn recv_with_fd(&mut self, buf: &mut [u8], fds: &mut [RawFd]) -> io::Result<(usize, usize)> { + future::poll_fn(|cx| self.poll_recv_with_fd(cx, buf, fds)).await + } + /// Shuts down the read, write, or both halves of this connection. /// /// This function will cause all pending and future I/O calls on the @@ -136,6 +169,40 @@ impl UnixStream { } } +/// A UnixListener supports transferring FDs between processes +pub struct UnixListener { + io: AsyncFd, +} + +impl UnixListener { + /// Creates a new `UnixListener` bound to the specified socket. + pub fn bind>(path: P) -> io::Result { + Ok(UnixListener { + io: AsyncFd::new(MioUnixListener::bind(path)?)?, + }) + } + + /// Accepts a new incoming connection to this listener. + pub fn poll_accept(&self, cx: &mut Context<'_>) -> Poll> { + loop { + let mut read_guard = ready!(self.io.poll_read_ready(cx))?; + + match self.io.get_ref().accept() { + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + read_guard.clear_ready(); + } + Ok((stream, peer_addr)) => return Poll::Ready(Ok((UnixStream::from_mio(stream)?, peer_addr))), + Err(err) => return Poll::Ready(Err(err)), + } + } + } + + /// Accepts a new incoming connection to this listener. + pub async fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { + future::poll_fn(|cx| self.poll_accept(cx)).await + } +} + /// A common implementation of `sendmsg` that sends provided bytes with ancillary file descriptors /// over either a datagram or stream unix socket. /// @@ -189,3 +256,78 @@ fn send_with_fd(socket: RawFd, bs: &[u8], fds: &[RawFd]) -> io::Result { } } } + +/// A common implementation of `recvmsg` that receives provided bytes and the ancillary file +/// descriptors over either a datagram or stream unix socket. +/// +/// Borrowed from: https://github.com/Standard-Cognition/sendfd +fn recv_with_fd(socket: RawFd, bs: &mut [u8], mut fds: &mut [RawFd]) -> io::Result<(usize, usize)> { + unsafe { + let mut iov = libc::iovec { + iov_base: bs.as_mut_ptr() as *mut _, + iov_len: bs.len(), + }; + + // Construct msghdr + // + // 1. Allocate memory for msg_control + let cmsg_fd_len = fds.len() * mem::size_of::(); + let cmsg_buffer_len = libc::CMSG_SPACE(cmsg_fd_len as u32) as usize; + let mut cmsg_buffer = Vec::with_capacity(cmsg_buffer_len); + cmsg_buffer.set_len(cmsg_buffer_len); + + let mut msghdr = libc::msghdr { + msg_name: ptr::null_mut(), + msg_namelen: 0, + msg_iov: &mut iov as *mut _, + msg_iovlen: 1, + msg_control: cmsg_buffer.as_mut_ptr(), + msg_controllen: cmsg_buffer_len.try_into().unwrap(), + ..mem::zeroed() + }; + + let count = libc::recvmsg(socket, &mut msghdr as *mut _, 0); + if count < 0 { + let error = io::Error::last_os_error(); + return Err(error); + } + + // Walk the ancillary data buffer and copy the raw descriptors from it into the output + // buffer. + let mut descriptor_count = 0; + let mut cmsg_header = libc::CMSG_FIRSTHDR(&mut msghdr as *mut _); + while !cmsg_header.is_null() { + if (*cmsg_header).cmsg_level == libc::SOL_SOCKET && (*cmsg_header).cmsg_type == libc::SCM_RIGHTS { + let data_ptr = libc::CMSG_DATA(cmsg_header); + let data_offset = data_ptr.offset_from(cmsg_header as *const _); + debug_assert!(data_offset >= 0); + let data_byte_count = (*cmsg_header).cmsg_len as usize - data_offset as usize; + debug_assert!((*cmsg_header).cmsg_len as isize > data_offset); + debug_assert!(data_byte_count % mem::size_of::() == 0); + let rawfd_count = (data_byte_count / mem::size_of::()) as isize; + let fd_ptr = data_ptr as *const RawFd; + for i in 0..rawfd_count { + if let Some((dst, rest)) = { fds }.split_first_mut() { + *dst = ptr::read_unaligned(fd_ptr.offset(i)); + descriptor_count += 1; + fds = rest; + } else { + // This branch is unreachable. We allocate the ancillary data buffer just + // large enough to fit exactly the number of `RawFd`s that are in the `fds` + // buffer. It is not possible for the OS to return more of them. + // + // If this branch ended up being reachable for some reason, it would be + // necessary for this branch to close the file descriptors to avoid leaking + // resources. + // + // TODO: consider using unreachable_unchecked + unreachable!(); + } + } + } + cmsg_header = libc::CMSG_NXTHDR(&mut msghdr as *mut _, cmsg_header); + } + + Ok((count as usize, descriptor_count)) + } +} diff --git a/tests/socks4.rs b/tests/socks4.rs index 83cd408f..246b6750 100644 --- a/tests/socks4.rs +++ b/tests/socks4.rs @@ -45,7 +45,10 @@ impl Socks4TestServer { }, cli_config: { let mut cfg = Config::new(ConfigType::Local); - cfg.local = vec![LocalConfig::new(ServerAddr::from(local_addr), ProtocolType::Socks)]; + cfg.local = vec![LocalConfig::new_with_addr( + ServerAddr::from(local_addr), + ProtocolType::Socks, + )]; cfg.server = vec![ServerConfig::new(svr_addr, pwd.to_owned(), method)]; cfg }, diff --git a/tests/socks5.rs b/tests/socks5.rs index 81f34e96..1872e6eb 100644 --- a/tests/socks5.rs +++ b/tests/socks5.rs @@ -47,7 +47,10 @@ impl Socks5TestServer { }, cli_config: { let mut cfg = Config::new(ConfigType::Local); - cfg.local = vec![LocalConfig::new(ServerAddr::from(local_addr), ProtocolType::Socks)]; + cfg.local = vec![LocalConfig::new_with_addr( + ServerAddr::from(local_addr), + ProtocolType::Socks, + )]; cfg.local[0].mode = if enable_udp { Mode::TcpAndUdp } else { Mode::TcpOnly }; cfg.server = vec![ServerConfig::new(svr_addr, pwd.to_owned(), method)]; cfg diff --git a/tests/udp.rs b/tests/udp.rs index cbfbb0b4..e6d526e4 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -35,7 +35,10 @@ fn get_svr_config() -> Config { fn get_cli_config() -> Config { let mut cfg = Config::new(ConfigType::Local); - cfg.local = vec![LocalConfig::new(LOCAL_ADDR.parse().unwrap(), ProtocolType::Socks)]; + cfg.local = vec![LocalConfig::new_with_addr( + LOCAL_ADDR.parse().unwrap(), + ProtocolType::Socks, + )]; cfg.local[0].mode = Mode::TcpAndUdp; cfg.server = vec![ServerConfig::new( SERVER_ADDR.parse::().unwrap(),