diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index 9692447d3..544948c4e 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "84b5c3544819a9bb25474d15a2a6a184931793feebe911fce0c60665e0053ffb", + "checksum": "8cbed76fd51e27aadaf5837fea83402a02fc001c7c4a9d9817f07e77087dcaae", "crates": { "addr2line 0.20.0": { "name": "addr2line", @@ -532,6 +532,36 @@ }, "license": "BSD-3-Clause" }, + "antidote 1.0.0": { + "name": "antidote", + "version": "1.0.0", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/antidote/1.0.0/download", + "sha256": "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" + } + }, + "targets": [ + { + "Library": { + "crate_name": "antidote", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "antidote", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.0.0" + }, + "license": "MIT/Apache-2.0" + }, "anyhow 1.0.72": { "name": "anyhow", "version": "1.0.72", @@ -682,6 +712,53 @@ }, "license": "MIT OR Apache-2.0" }, + "async-recursion 1.0.4": { + "name": "async-recursion", + "version": "1.0.4", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/async-recursion/1.0.4/download", + "sha256": "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "async_recursion", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "async_recursion", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.66", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.32", + "target": "quote" + }, + { + "id": "syn 2.0.27", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.0.4" + }, + "license": "MIT OR Apache-2.0" + }, "async-trait 0.1.72": { "name": "async-trait", "version": "0.1.72", @@ -1127,7 +1204,20 @@ "build_script_attrs": { "data_glob": [ "**" - ] + ], + "link_deps": { + "common": [ + { + "id": "clang-sys 1.6.1", + "target": "clang_sys" + }, + { + "id": "prettyplease 0.2.12", + "target": "prettyplease" + } + ], + "selects": {} + } }, "license": "BSD-3-Clause" }, @@ -2575,6 +2665,10 @@ "id": "hyper 0.14.27", "target": "hyper" }, + { + "id": "hyper-boring 2.1.2", + "target": "hyper_boring" + }, { "id": "ipnet 2.8.0", "target": "ipnet" @@ -2721,6 +2815,10 @@ "edition": "2021", "proc_macro_deps": { "common": [ + { + "id": "async-recursion 1.0.4", + "target": "async_recursion" + }, { "id": "async-trait 0.1.72", "target": "async_trait" @@ -6266,6 +6364,7 @@ "h2", "http1", "http2", + "runtime", "server", "socket2", "tcp" @@ -6346,6 +6445,87 @@ }, "license": "MIT" }, + "hyper-boring 2.1.2": { + "name": "hyper-boring", + "version": "2.1.2", + "repository": { + "Git": { + "remote": "https://github.com/Watfaq/boring.git", + "commitish": { + "Branch": "bazel" + }, + "strip_prefix": "hyper-boring" + } + }, + "targets": [ + { + "Library": { + "crate_name": "hyper_boring", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "hyper_boring", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "runtime" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "antidote 1.0.0", + "target": "antidote" + }, + { + "id": "boring 2.1.0", + "target": "boring" + }, + { + "id": "http 0.2.9", + "target": "http" + }, + { + "id": "hyper 0.14.27", + "target": "hyper" + }, + { + "id": "linked_hash_set 0.1.4", + "target": "linked_hash_set" + }, + { + "id": "once_cell 1.18.0", + "target": "once_cell" + }, + { + "id": "tokio 1.29.1", + "target": "tokio" + }, + { + "id": "tokio-boring 2.1.5", + "target": "tokio_boring" + }, + { + "id": "tower-layer 0.3.2", + "target": "tower_layer" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "2.1.2" + }, + "license": "MIT/Apache-2.0" + }, "idna 0.2.3": { "name": "idna", "version": "0.2.3", @@ -7254,6 +7434,45 @@ }, "license": "MIT/Apache-2.0" }, + "linked_hash_set 0.1.4": { + "name": "linked_hash_set", + "version": "0.1.4", + "repository": { + "Http": { + "url": "https://crates.io/api/v1/crates/linked_hash_set/0.1.4/download", + "sha256": "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" + } + }, + "targets": [ + { + "Library": { + "crate_name": "linked_hash_set", + "crate_root": "src/lib.rs", + "srcs": [ + "**/*.rs" + ] + } + } + ], + "library_target_name": "linked_hash_set", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "linked-hash-map 0.5.6", + "target": "linked_hash_map" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.1.4" + }, + "license": "Apache-2.0" + }, "linux-raw-sys 0.4.3": { "name": "linux-raw-sys", "version": "0.4.3", @@ -11069,7 +11288,16 @@ "build_script_attrs": { "data_glob": [ "**" - ] + ], + "link_deps": { + "common": [ + { + "id": "ring 0.16.20", + "target": "ring" + } + ], + "selects": {} + } }, "license": "Apache-2.0/ISC/MIT" }, diff --git a/Cargo.lock b/Cargo.lock index 715f0640c..bc1947c01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,53 +113,10 @@ dependencies = [ ] [[package]] -name = "anstream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" - -[[package]] -name = "anstyle-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" +name = "antidote" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "anstyle-wincon" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" -dependencies = [ - "anstyle", - "windows-sys 0.48.0", -] +checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" [[package]] name = "anyhow" @@ -185,6 +142,17 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + [[package]] name = "async-trait" version = "0.1.72" @@ -471,44 +439,42 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "clap_builder", + "atty", + "bitflags 1.3.2", "clap_derive", - "once_cell", -] - -[[package]] -name = "clap_builder" -version = "4.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" -dependencies = [ - "anstream", - "anstyle", "clap_lex", + "indexmap 1.9.3", + "once_cell", "strsim", + "termcolor", + "textwrap", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", + "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.27", + "syn 1.0.109", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] [[package]] name = "clash" @@ -524,6 +490,7 @@ version = "0.1.0" dependencies = [ "aes-gcm 0.10.2", "anyhow", + "async-recursion", "async-trait", "atty", "base64 0.21.2", @@ -544,6 +511,7 @@ dependencies = [ "http", "httparse", "hyper", + "hyper-boring", "ipnet", "libc", "lru_time_cache", @@ -554,7 +522,7 @@ dependencies = [ "prost", "rand", "regex", - "rustls 0.21.5", + "rustls", "security-framework", "serde", "serde_yaml", @@ -566,7 +534,7 @@ dependencies = [ "thiserror", "tokio", "tokio-boring", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-tungstenite", "tokio-util", "tower", @@ -588,12 +556,6 @@ dependencies = [ "cc", ] -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - [[package]] name = "const-oid" version = "0.7.1" @@ -1268,6 +1230,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-boring" +version = "2.1.2" +source = "git+https://github.com/Watfaq/boring.git?branch=bazel#d58d4367cb21a44d301d295b9259f755eb302041" +dependencies = [ + "antidote", + "boring", + "http", + "hyper", + "linked_hash_set", + "once_cell", + "tokio", + "tokio-boring", + "tower-layer", +] + [[package]] name = "idna" version = "0.2.3" @@ -1365,17 +1343,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi 0.3.2", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1454,6 +1421,15 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linked_hash_set" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "linux-raw-sys" version = "0.4.3" @@ -1713,6 +1689,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" + [[package]] name = "overload" version = "0.1.1" @@ -1900,6 +1882,30 @@ dependencies = [ "syn 2.0.27", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -2128,18 +2134,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustls" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - [[package]] name = "rustls-pemfile" version = "1.0.3" @@ -2149,16 +2143,6 @@ dependencies = [ "base64 0.21.2", ] -[[package]] -name = "rustls-webpki" -version = "0.101.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.14" @@ -2249,18 +2233,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.179" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.179" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b" dependencies = [ "proc-macro2", "quote", @@ -2520,12 +2504,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "termtree" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.44" @@ -2635,21 +2634,11 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.8", + "rustls", "tokio", "webpki", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.5", - "tokio", -] - [[package]] name = "tokio-tfo" version = "0.2.1" @@ -2669,9 +2658,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.20.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", @@ -2847,13 +2836,13 @@ dependencies = [ "ipnet", "lazy_static", "rand", - "rustls 0.20.8", + "rustls", "rustls-pemfile", "smallvec", "thiserror", "tinyvec", "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls", "tracing", "url", "webpki", @@ -2888,13 +2877,13 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.20.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ + "base64 0.13.1", "byteorder", "bytes", - "data-encoding", "http", "httparse", "log", @@ -2981,12 +2970,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - [[package]] name = "uuid" version = "1.4.1" diff --git a/clash/tests/data/config/Country.mmdb b/clash/tests/data/config/Country.mmdb new file mode 100644 index 000000000..7b8363c56 Binary files /dev/null and b/clash/tests/data/config/Country.mmdb differ diff --git a/clash/tests/data/config/rules.yaml b/clash/tests/data/config/rules.yaml new file mode 100644 index 000000000..2119a8f7b --- /dev/null +++ b/clash/tests/data/config/rules.yaml @@ -0,0 +1,105 @@ +--- +port: 8888 +socks-port: 8889 +mixed-port: 8899 + + +dns: + enable: true + listen: 127.0.0.1:53533 + # ipv6: false # when the false, response to AAAA questions will be empty + + # These nameservers are used to resolve the DNS nameserver hostnames below. + # Specify IP addresses only + default-nameserver: + - 114.114.114.114 + - 8.8.8.8 + enhanced-mode: fake-ip # or fake-ip + fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR + # use-hosts: true # lookup hosts and return IP record + + # Hostnames in this list will not be resolved with fake IPs + # i.e. questions to these domain names will always be answered with their + # real IP addresses + # fake-ip-filter: + # - '*.lan' + # - localhost.ptlogin2.qq.com + + # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. + # All DNS questions are sent directly to the nameserver, without proxies + # involved. Clash answers the DNS question with the first result gathered. + nameserver: + - 114.114.114.114 # default value + - 8.8.8.8 # default value + - tls://dns.google:853 # DNS over TLS + - https://1.1.1.1/dns-query # DNS over HTTPS + - dhcp://en0 # dns from dhcp + +allow-lan: true +mode: rule +log-level: debug +external-controller: 127.0.0.1:6170 +experimental: + ignore-resolve-fail: true + +proxy-groups: + - name: "relay" + type: relay + proxies: + - "ss" + - DIRECT + + # - name: "auto" + # type: url-test + # proxies: + # - "ss" + # - DIRECT + # url: "http://www.gstatic.com/generate_204" + # interval: 300 + + # - name: "fallback-auto" + # type: fallback + # proxies: + # - "ss" + # - DIRECT + # url: "http://www.gstatic.com/generate_204" + # interval: 300 + + # - name: "load-balance" + # type: load-balance + # proxies: + # - "ss" + # - DIRECT + # strategy: round-robin + # url: "http://www.gstatic.com/generate_204" + # interval: 300 + + # - name: select + # type: select + # proxies: + # - "ss" + # - DIRECT + # - REJECT + + +proxies: + - name: "ss" + type: ss + server: localhost + port: 8388 + cipher: aes-256-gcm + password: "password" + udp: true + +rules: + - DOMAIN,ipinfo.io,relay + - DOMAIN-SUFFIX,google.com,ss + - DOMAIN-SUFFIX,facebook.com,REJECT + - DOMAIN-KEYWORD,google,ss + - DOMAIN,google.com,ss + - SRC-IP-CIDR,192.168.1.1/24,DIRECT + - GEOIP,CN,DIRECT + - DST-PORT,80,DIRECT + - SRC-PORT,7777,DIRECT + - MATCH, DIRECT +... diff --git a/clash/tests/data/config/simple.yaml b/clash/tests/data/config/simple.yaml new file mode 100644 index 000000000..fa7833683 --- /dev/null +++ b/clash/tests/data/config/simple.yaml @@ -0,0 +1,47 @@ +--- +port: 8888 +socks-port: 8889 +mixed-port: 8899 + + +dns: + enable: true + listen: 127.0.0.1:53533 + # ipv6: false # when the false, response to AAAA questions will be empty + + # These nameservers are used to resolve the DNS nameserver hostnames below. + # Specify IP addresses only + default-nameserver: + - 114.114.114.114 + - 8.8.8.8 + enhanced-mode: fake-ip # or fake-ip + fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR + # use-hosts: true # lookup hosts and return IP record + + # Hostnames in this list will not be resolved with fake IPs + # i.e. questions to these domain names will always be answered with their + # real IP addresses + # fake-ip-filter: + # - '*.lan' + # - localhost.ptlogin2.qq.com + + # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. + # All DNS questions are sent directly to the nameserver, without proxies + # involved. Clash answers the DNS question with the first result gathered. + nameserver: + - 114.114.114.114 # default value + - 8.8.8.8 # default value + - tls://dns.google:853 # DNS over TLS + - https://1.1.1.1/dns-query # DNS over HTTPS + - dhcp://en0 # dns from dhcp + +allow-lan: true +mode: rule +log-level: debug +external-controller: 127.0.0.1:6170 +experimental: + ignore-resolve-fail: true + +rules: + - MATCH, DIRECT +... diff --git a/clash/tests/data/config/ss-obfs.yaml b/clash/tests/data/config/ss-obfs.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/clash/tests/data/config/ss.yaml b/clash/tests/data/config/ss.yaml new file mode 100644 index 000000000..7af92b387 --- /dev/null +++ b/clash/tests/data/config/ss.yaml @@ -0,0 +1,56 @@ +--- +port: 8888 +socks-port: 8889 +mixed-port: 8899 + + +dns: + enable: true + listen: 127.0.0.1:53533 + # ipv6: false # when the false, response to AAAA questions will be empty + + # These nameservers are used to resolve the DNS nameserver hostnames below. + # Specify IP addresses only + default-nameserver: + - 114.114.114.114 + - 8.8.8.8 + enhanced-mode: fake-ip # or fake-ip + fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR + # use-hosts: true # lookup hosts and return IP record + + # Hostnames in this list will not be resolved with fake IPs + # i.e. questions to these domain names will always be answered with their + # real IP addresses + # fake-ip-filter: + # - '*.lan' + # - localhost.ptlogin2.qq.com + + # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. + # All DNS questions are sent directly to the nameserver, without proxies + # involved. Clash answers the DNS question with the first result gathered. + nameserver: + - 114.114.114.114 # default value + - 8.8.8.8 # default value + - tls://dns.google:853 # DNS over TLS + - https://1.1.1.1/dns-query # DNS over HTTPS + - dhcp://en0 # dns from dhcp + +allow-lan: true +mode: rule +log-level: debug +external-controller: 127.0.0.1:6170 +experimental: + ignore-resolve-fail: true + +proxies: + - name: "ss" + type: ss + server: localhost + port: 8388 + cipher: aes-256-gcm + password: "password" + udp: true + +rules: + - MATCH, ss +... diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index 7edd2c18d..7b407f102 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -15,6 +15,7 @@ async-trait = "0.1" anyhow = "1.0" futures = "0.3" bytes = "1.1" +async-recursion = "1" ipnet = "2.5" url = "2.2" regex = "1" @@ -34,6 +35,7 @@ base64 = "0.21" uuid = { version = "1.2.1", features = ["v4", "fast-rng", "macro-diagnostics"] } boring = { git = "https://github.com/Watfaq/boring.git", branch = "bazel" } boring-sys = { git = "https://github.com/Watfaq/boring.git", branch = "bazel" } +hyper-boring = { git = "https://github.com/Watfaq/boring.git", branch = "bazel" } tokio-boring = { git = "https://github.com/Watfaq/boring.git", branch = "bazel" } crc32fast = "1.3.2" brotli = "3.3.4" diff --git a/clash_lib/src/app/dispatcher.rs b/clash_lib/src/app/dispatcher.rs index 996aedfef..a9d0a3586 100644 --- a/clash_lib/src/app/dispatcher.rs +++ b/clash_lib/src/app/dispatcher.rs @@ -66,7 +66,7 @@ impl Dispatcher { info!("remote connection established {}", sess); match copy_bidirectional(&mut lhs, &mut rhs).await { Ok((up, down)) => { - info!( + debug!( "connection {} closed with {} bytes up, {} bytes down", sess, up, down ); diff --git a/clash_lib/src/app/outbound/manager.rs b/clash_lib/src/app/outbound/manager.rs index f10f050e4..64a4d3cd3 100644 --- a/clash_lib/src/app/outbound/manager.rs +++ b/clash_lib/src/app/outbound/manager.rs @@ -64,7 +64,9 @@ impl OutboundManager { for outbound_group in outbound_groups.iter() { match outbound_group { - OutboundGroupProtocol::Relay(_proto) => {} + OutboundGroupProtocol::Relay(proto) => { + handlers.insert(proto.name.clone(), proto.try_into()?); + } OutboundGroupProtocol::UrlTest(_proto) => todo!(), OutboundGroupProtocol::Fallback(_proto) => todo!(), OutboundGroupProtocol::LoadBalance(_proto) => todo!(), diff --git a/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs b/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs index 7f1b03451..9d63a64fa 100644 --- a/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs +++ b/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs @@ -1,70 +1,15 @@ use super::{ProviderVehicle, ProviderVehicleType}; use crate::app::ThreadSafeDNSResolver; use crate::common::errors::map_io_error; -use crate::proxy::utils::new_tcp_stream; -use crate::proxy::AnyStream; -use async_trait::async_trait; +use crate::common::http::{new_http_client, HttpClient, LocalConnector}; -use hyper::client::connect::{Connected, Connection}; +use async_trait::async_trait; -use hyper::service::Service; use hyper::{body, Uri}; -use std::future::Future; use std::io; use std::path::{Path, PathBuf}; -use std::pin::Pin; -use std::task::{Context, Poll}; - -#[derive(Clone)] -struct LocalConnector(pub ThreadSafeDNSResolver); - -impl Service for LocalConnector { - type Response = AnyStream; - type Error = io::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, remote: Uri) -> Self::Future { - let host = remote - .host() - .expect(format!("invalid url: {}", remote.to_string()).as_str()) - .to_owned(); - - let dns = self.0.clone(); - - Box::pin(async move { - new_tcp_stream( - dns, - host.as_str(), - remote.port_u16().unwrap_or(match remote.scheme_str() { - None => 80, - Some(s) => match s { - s if s == "http" => 80, - s if s == "https" => 443, - _ => panic!("invalid url: {}", remote), - }, - }), - None, - #[cfg(any(target_os = "linux", target_os = "android"))] - None, - ) - .await - }) - } -} - -impl Connection for AnyStream { - fn connected(&self) -> Connected { - Connected::new() - } -} - -type HttpClient = hyper::Client; pub struct Vehicle { pub url: Uri, @@ -78,9 +23,7 @@ impl Vehicle { path: P, dns_resolver: ThreadSafeDNSResolver, ) -> Self { - let connector = LocalConnector(dns_resolver); - - let client = hyper::Client::builder().build::<_, hyper::Body>(connector); + let client = new_http_client(dns_resolver).expect("failed to create http client"); Self { url: url.into(), path: path.as_ref().to_path_buf(), diff --git a/clash_lib/src/app/router/mmdb.rs b/clash_lib/src/app/router/mmdb.rs index a83144830..15b3abaff 100644 --- a/clash_lib/src/app/router/mmdb.rs +++ b/clash_lib/src/app/router/mmdb.rs @@ -1,25 +1,125 @@ -use std::{net::IpAddr, path::Path}; +use std::{fs, io::Write, net::IpAddr, path::Path}; +use async_recursion::async_recursion; +use hyper::body::HttpBody; use maxminddb::geoip2; +use tracing::{debug, info, warn}; -use crate::{common::errors::map_io_error, Error}; +use crate::{ + common::{ + errors::{map_io_error, new_io_error}, + http::HttpClient, + }, + Error, +}; pub struct MMDB { reader: maxminddb::Reader>, } impl MMDB { - pub fn new>(path: P) -> anyhow::Result { + pub async fn new>( + path: P, + download_url: Option, + http_client: HttpClient, + ) -> anyhow::Result { + debug!("mmdb path: {}", path.as_ref().to_string_lossy()); + let cwd = std::env::current_dir().unwrap(); - let reader = maxminddb::Reader::open_readfile(&path).map_err(|x| { - Error::InvalidConfig(format!( - "cant open mmdb `{}/{}`: {}", - cwd.to_string_lossy(), - path.as_ref().to_string_lossy(), - x.to_string() - )) - })?; - Ok(MMDB { reader }) + let mmdb_file = Path::new(&cwd).join(&path); + + if !mmdb_file.exists() { + if let Some(url) = download_url.as_ref() { + info!("downloading mmdb from {}", url); + Self::download(url, &mmdb_file, &http_client).await?; + } else { + return Err(Error::InvalidConfig(format!( + "mmdb `{}/{}` not found and mmdb_download_url is not set", + cwd.to_string_lossy(), + path.as_ref().to_string_lossy() + )) + .into()); + } + } + + match maxminddb::Reader::open_readfile(&path) { + Ok(r) => Ok(MMDB { reader: r }), + Err(e) => match e { + maxminddb::MaxMindDBError::InvalidDatabaseError(_) + | maxminddb::MaxMindDBError::IoError(_) => { + warn!( + "invalid mmdb `{}/{}`: {}, trying to download again", + cwd.to_string_lossy(), + path.as_ref().to_string_lossy(), + e.to_string() + ); + + // try to download again + fs::remove_file(&mmdb_file)?; + if let Some(url) = download_url.as_ref() { + info!("downloading mmdb from {}", url); + Self::download(url, &mmdb_file, &http_client).await?; + Ok(MMDB { + reader: maxminddb::Reader::open_readfile(&path)?, + }) + } else { + return Err(Error::InvalidConfig(format!( + "mmdb `{}/{}` not found and mmdb_download_url is not set", + cwd.to_string_lossy(), + path.as_ref().to_string_lossy() + )) + .into()); + } + } + _ => Err(Error::InvalidConfig(format!( + "cant open mmdb `{}/{}`: {}", + cwd.to_string_lossy(), + path.as_ref().to_string_lossy(), + e.to_string() + )) + .into()), + }, + } + } + + #[async_recursion(?Send)] + async fn download>( + url: &str, + path: P, + http_client: &HttpClient, + ) -> anyhow::Result<()> { + let uri = url.parse::()?; + let mut out = std::fs::File::create(&path)?; + + let mut res = http_client.get(uri).await?; + + if res.status().is_redirection() { + return Self::download( + res.headers() + .get("Location") + .ok_or(new_io_error( + format!("failed to download from {}", url).as_str(), + ))? + .to_str()?, + path, + http_client, + ) + .await; + } + + if !res.status().is_success() { + return Err( + Error::InvalidConfig(format!("mmdb download failed: {}", res.status())).into(), + ); + } + + debug!("downloading mmdb to {}", path.as_ref().to_string_lossy()); + + while let Some(chunk) = res.body_mut().data().await { + out.write_all(&chunk?)?; + } + + Ok(()) } pub fn lookup(&self, ip: IpAddr) -> anyhow::Result { diff --git a/clash_lib/src/app/router/mod.rs b/clash_lib/src/app/router/mod.rs index ae9eb85e2..46032b44a 100644 --- a/clash_lib/src/app/router/mod.rs +++ b/clash_lib/src/app/router/mod.rs @@ -6,19 +6,21 @@ use crate::app::router::rules::ruleset::RuleSet; use crate::app::router::rules::RuleMatcher; use crate::app::ThreadSafeDNSResolver; +use crate::common::http::new_http_client; use crate::config::internal::rule::Rule; use crate::session::{Session, SocksAddr}; use crate::app::router::rules::final_::Final; use std::sync::Arc; use tokio::sync::RwLock; +use tracing::info; mod mmdb; mod rules; pub struct Router { rules: Vec>, - dns_client: ThreadSafeDNSResolver, + dns_resolver: ThreadSafeDNSResolver, } pub type ThreadSafeRouter = Arc>; @@ -26,8 +28,18 @@ pub type ThreadSafeRouter = Arc>; const MATCH: &str = "MATCH"; impl Router { - pub fn new(rules: Vec, dns_client: ThreadSafeDNSResolver, mmdb_path: String) -> Self { - let mmdb = Arc::new(mmdb::MMDB::new(mmdb_path).expect("failed to load mmdb")); + pub async fn new( + rules: Vec, + dns_resolver: ThreadSafeDNSResolver, + mmdb_path: String, + mmdb_download_url: Option, + ) -> Self { + let client = new_http_client(dns_resolver.clone()).expect("failed to create http client"); + let mmdb = Arc::new( + mmdb::MMDB::new(mmdb_path, mmdb_download_url, client) + .await + .expect("failed to load mmdb"), + ); Self { rules: rules @@ -97,7 +109,7 @@ impl Router { Rule::Match { target } => Box::new(Final { target }), }) .collect(), - dns_client, + dns_resolver, } } @@ -108,7 +120,7 @@ impl Router { for r in self.rules.iter() { if sess.destination.is_domain() && r.should_resolve_ip() && !sess_resolved { if let Ok(ip) = self - .dns_client + .dns_resolver .resolve(sess.destination.domain().unwrap()) .await { @@ -120,6 +132,7 @@ impl Router { } if r.apply(&sess_dup) { + info!("matched {} to target {}", &sess_dup, r.target()); return r.target(); } } diff --git a/clash_lib/src/common/http.rs b/clash_lib/src/common/http.rs new file mode 100644 index 000000000..84f705773 --- /dev/null +++ b/clash_lib/src/common/http.rs @@ -0,0 +1,78 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use boring::ssl::{SslConnector, SslMethod}; +use futures::Future; +use http::Uri; +use hyper::client::connect::{Connected, Connection}; +use hyper_boring::HttpsConnector; +use tower::Service; + +use crate::{ + app::ThreadSafeDNSResolver, + proxy::{utils::new_tcp_stream, AnyStream}, +}; + +use super::errors::map_io_error; + +#[derive(Clone)] +pub struct LocalConnector(pub ThreadSafeDNSResolver); + +impl Service for LocalConnector { + type Response = AnyStream; + type Error = std::io::Error; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, remote: Uri) -> Self::Future { + let host = remote + .host() + .expect(format!("invalid url: {}", remote.to_string()).as_str()) + .to_owned(); + + let dns = self.0.clone(); + + Box::pin(async move { + new_tcp_stream( + dns, + host.as_str(), + remote.port_u16().unwrap_or(match remote.scheme_str() { + None => 80, + Some(s) => match s { + s if s == "http" => 80, + s if s == "https" => 443, + _ => panic!("invalid url: {}", remote), + }, + }), + None, + #[cfg(any(target_os = "linux", target_os = "android"))] + None, + ) + .await + }) + } +} + +impl Connection for AnyStream { + fn connected(&self) -> Connected { + Connected::new() + } +} + +pub type HttpClient = hyper::Client>; + +pub fn new_http_client(dns_resolver: ThreadSafeDNSResolver) -> std::io::Result { + let connector = LocalConnector(dns_resolver); + + let mut ssl = SslConnector::builder(SslMethod::tls()).map_err(map_io_error)?; + ssl.set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(map_io_error)?; + + let connector = HttpsConnector::with_connector(connector, ssl).map_err(map_io_error)?; + Ok(hyper::Client::builder().build::<_, hyper::Body>(connector)) +} diff --git a/clash_lib/src/common/mod.rs b/clash_lib/src/common/mod.rs index 35c6999cd..a7cb78a77 100644 --- a/clash_lib/src/common/mod.rs +++ b/clash_lib/src/common/mod.rs @@ -1,5 +1,6 @@ pub mod crypto; pub mod errors; +pub mod http; pub mod tls; pub mod trie; pub mod utils; diff --git a/clash_lib/src/config/def.rs b/clash_lib/src/config/def.rs index fff218f00..4073c0911 100644 --- a/clash_lib/src/config/def.rs +++ b/clash_lib/src/config/def.rs @@ -55,6 +55,7 @@ pub struct Config { pub rule: Vec, pub hosts: HashMap, pub mmdb: String, + pub mmdb_download_url: Option, /// these options has default vals, /// and needs extra processing @@ -130,6 +131,10 @@ impl Default for Config { proxy_group: Default::default(), rule: Default::default(), mmdb: "Country.mmdb".to_string(), + mmdb_download_url: Some( + "https://github.com/Loyalsoldier/geoip/releases/download/202307271745/Country.mmdb" + .to_owned(), + ), } } } diff --git a/clash_lib/src/config/internal/config.rs b/clash_lib/src/config/internal/config.rs index 5323f6e02..26a885776 100644 --- a/clash_lib/src/config/internal/config.rs +++ b/clash_lib/src/config/internal/config.rs @@ -69,6 +69,7 @@ impl TryFrom for Config { }), routing_mask: c.routing_mask, mmdb: c.mmdb.to_owned(), + mmdb_download_url: c.mmdb_download_url.to_owned(), }, dns: (&c).try_into()?, experimental: c.experimental, @@ -164,6 +165,7 @@ pub struct General { interface: Option, routing_mask: Option, pub mmdb: String, + pub mmdb_download_url: Option, } pub struct Profile { diff --git a/clash_lib/src/lib.rs b/clash_lib/src/lib.rs index 2d38642d4..afb6a2f3c 100644 --- a/clash_lib/src/lib.rs +++ b/clash_lib/src/lib.rs @@ -105,6 +105,7 @@ async fn start_async(opts: Options) -> Result<(), Error> { let mut runners = Vec::new(); let default_dns_resolver = Arc::new(dns::Resolver::new(config.dns).await); + let outbound_manager = Arc::new(RwLock::new(OutboundManager::new( config .proxies @@ -125,11 +126,16 @@ async fn start_async(opts: Options) -> Result<(), Error> { default_dns_resolver.clone(), )?)); - let router = Arc::new(RwLock::new(Router::new( - config.rules, - default_dns_resolver.clone(), - config.general.mmdb, - ))); + let router = Arc::new(RwLock::new( + Router::new( + config.rules, + default_dns_resolver.clone(), + config.general.mmdb, + config.general.mmdb_download_url, + ) + .await, + )); + let dispatcher = Arc::new(Dispatcher::new( outbound_manager, router, diff --git a/clash_lib/src/proxy/converters/mod.rs b/clash_lib/src/proxy/converters/mod.rs new file mode 100644 index 000000000..481774dc8 --- /dev/null +++ b/clash_lib/src/proxy/converters/mod.rs @@ -0,0 +1,2 @@ +pub mod relay; +pub mod shadowsocks; diff --git a/clash_lib/src/proxy/converters/relay.rs b/clash_lib/src/proxy/converters/relay.rs new file mode 100644 index 000000000..340d952ba --- /dev/null +++ b/clash_lib/src/proxy/converters/relay.rs @@ -0,0 +1,29 @@ +use crate::{ + config::internal::proxy::OutboundGroupRelay, + proxy::{ + relay::{Handler, HandlerOptions}, + AnyOutboundHandler, CommonOption, + }, +}; + +impl TryFrom for AnyOutboundHandler { + type Error = crate::Error; + + fn try_from(value: OutboundGroupRelay) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&OutboundGroupRelay> for AnyOutboundHandler { + type Error = crate::Error; + + fn try_from(value: &OutboundGroupRelay) -> Result { + Ok(Handler::new( + HandlerOptions { + name: value.name.to_owned(), + common_opts: CommonOption::default(), + }, + vec![], + )) + } +} diff --git a/clash_lib/src/proxy/converters/shadowsocks.rs b/clash_lib/src/proxy/converters/shadowsocks.rs new file mode 100644 index 000000000..203bd2c93 --- /dev/null +++ b/clash_lib/src/proxy/converters/shadowsocks.rs @@ -0,0 +1,61 @@ +use crate::{ + config::internal::proxy::OutboundShadowsocks, + proxy::{ + shadowsocks::{Handler, HandlerOptions, OBFSOption}, + AnyOutboundHandler, CommonOption, + }, + Error, +}; + +impl TryFrom for AnyOutboundHandler { + type Error = crate::Error; + + fn try_from(value: OutboundShadowsocks) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&OutboundShadowsocks> for AnyOutboundHandler { + type Error = crate::Error; + + fn try_from(s: &OutboundShadowsocks) -> Result { + let h = Handler::new(HandlerOptions { + name: s.name.to_owned(), + common_opts: CommonOption::default(), + server: s.server.to_owned(), + port: s.port, + password: s.password.to_owned(), + cipher: s.cipher.to_owned(), + plugin_opts: match &s.plugin { + Some(plugin) => match plugin.as_str() { + "obfs" => s + .plugin_opts + .clone() + .ok_or(Error::InvalidConfig( + "plugin_opts is required for plugin obfs".to_owned(), + ))? + .try_into() + .map(|x| OBFSOption::Simple(x)) + .ok(), + "v2ray-plugin" => s + .plugin_opts + .clone() + .ok_or(Error::InvalidConfig( + "plugin_opts is required for plugin obfs".to_owned(), + ))? + .try_into() + .map(|x| OBFSOption::V2Ray(x)) + .ok(), + _ => { + return Err(Error::InvalidConfig(format!( + "unsupported plugin: {}", + plugin + ))); + } + }, + None => None, + }, + }); + Ok(h) + } +} diff --git a/clash_lib/src/proxy/mod.rs b/clash_lib/src/proxy/mod.rs index ca340fd0c..5c714e2a0 100644 --- a/clash_lib/src/proxy/mod.rs +++ b/clash_lib/src/proxy/mod.rs @@ -23,6 +23,8 @@ pub mod socks; pub mod utils; //pub mod vmess; +pub mod converters; + // proxy groups mod relay; @@ -40,8 +42,8 @@ pub enum ProxyError { Socks5(String), } -pub trait ProxyStream: AsyncRead + AsyncWrite + Send + Sync + Unpin {} -impl ProxyStream for T where T: AsyncRead + AsyncWrite + Send + Sync + Unpin {} +pub trait ProxyStream: AsyncRead + AsyncWrite + Send + Sync + Unpin + Debug {} +impl ProxyStream for T where T: AsyncRead + AsyncWrite + Send + Sync + Unpin + Debug {} pub type AnyStream = Box; pub trait InboundDatagram: diff --git a/clash_lib/src/proxy/relay/mod.rs b/clash_lib/src/proxy/relay/mod.rs index 014f46c22..f80ac3746 100644 --- a/clash_lib/src/proxy/relay/mod.rs +++ b/clash_lib/src/proxy/relay/mod.rs @@ -32,7 +32,11 @@ impl Handler { } async fn get_proxies(&self) -> Vec { - todo!("get proxies from providers") + futures::future::join_all(self.providers.iter().map(|x| x.proxies())) + .await + .into_iter() + .flatten() + .collect::>() } } diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 75548e342..b95ad751b 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -1,5 +1,6 @@ mod datagram; mod obfs; +mod stream; mod v2ray; use async_trait::async_trait; @@ -18,7 +19,7 @@ use crate::{ }; use std::{collections::HashMap, io, sync::Arc}; -use self::datagram::OutboundDatagramShadowsocks; +use self::{datagram::OutboundDatagramShadowsocks, stream::ShadowSocksStream}; use super::{ utils::{new_tcp_stream, new_udp_socket}, @@ -148,59 +149,6 @@ impl Handler { } } -impl TryFrom for AnyOutboundHandler { - type Error = crate::Error; - - fn try_from(value: OutboundShadowsocks) -> Result { - (&value).try_into() - } -} - -impl TryFrom<&OutboundShadowsocks> for AnyOutboundHandler { - type Error = crate::Error; - - fn try_from(s: &OutboundShadowsocks) -> Result { - let h = Handler::new(HandlerOptions { - name: s.name.to_owned(), - common_opts: CommonOption::default(), - server: s.server.to_owned(), - port: s.port, - password: s.password.to_owned(), - cipher: s.cipher.to_owned(), - plugin_opts: match &s.plugin { - Some(plugin) => match plugin.as_str() { - "obfs" => s - .plugin_opts - .clone() - .ok_or(Error::InvalidConfig( - "plugin_opts is required for plugin obfs".to_owned(), - ))? - .try_into() - .map(|x| OBFSOption::Simple(x)) - .ok(), - "v2ray-plugin" => s - .plugin_opts - .clone() - .ok_or(Error::InvalidConfig( - "plugin_opts is required for plugin obfs".to_owned(), - ))? - .try_into() - .map(|x| OBFSOption::V2Ray(x)) - .ok(), - _ => { - return Err(Error::InvalidConfig(format!( - "unsupported plugin: {}", - plugin - ))); - } - }, - None => None, - }, - }); - Ok(h) - } -} - #[async_trait] impl OutboundHandler for Handler { fn name(&self) -> &str { @@ -279,7 +227,7 @@ impl OutboundHandler for Handler { (sess.destination.host(), sess.destination.port()), ); - Ok(Box::new(stream)) + Ok(Box::new(ShadowSocksStream(stream))) } async fn connect_datagram( diff --git a/clash_lib/src/proxy/shadowsocks/stream.rs b/clash_lib/src/proxy/shadowsocks/stream.rs new file mode 100644 index 000000000..91f81f3ca --- /dev/null +++ b/clash_lib/src/proxy/shadowsocks/stream.rs @@ -0,0 +1,47 @@ +use std::{fmt::Debug, pin::Pin}; + +use shadowsocks::ProxyClientStream; +use tokio::io::{AsyncRead, AsyncWrite}; + +use crate::proxy::AnyStream; + +pub struct ShadowSocksStream(pub ProxyClientStream); +impl Debug for ShadowSocksStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ShadowSocksStream").finish() + } +} + +impl AsyncRead for ShadowSocksStream { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + Pin::new(&mut self.get_mut().0).poll_read(cx, buf) + } +} + +impl AsyncWrite for ShadowSocksStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + Pin::new(&mut self.get_mut().0).poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + Pin::new(&mut self.get_mut().0).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + Pin::new(&mut self.get_mut().0).poll_shutdown(cx) + } +} diff --git a/clash_lib/src/proxy/transport/grpc.rs b/clash_lib/src/proxy/transport/grpc.rs index 54f37d85b..d7c9eca69 100644 --- a/clash_lib/src/proxy/transport/grpc.rs +++ b/clash_lib/src/proxy/transport/grpc.rs @@ -10,6 +10,7 @@ use prost::encoding::decode_varint; use prost::encoding::encode_varint; use tracing::log; +use std::fmt::Debug; use std::future::Future; use std::io; use std::io::{Error, ErrorKind}; @@ -66,6 +67,18 @@ pub struct GrpcStream { payload_len: u64, } +impl Debug for GrpcStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GrpcStream") + .field("resp_fut", &self.resp_fut) + .field("recv", &self.recv) + .field("send", &self.send) + .field("buffer", &self.buffer) + .field("payload_len", &self.payload_len) + .finish() + } +} + impl GrpcStream { pub fn new(resp_fut: h2::client::ResponseFuture, send: SendStream) -> Self { Self { diff --git a/clash_lib/src/proxy/transport/h2.rs b/clash_lib/src/proxy/transport/h2.rs index 0a7288ca9..753299a8b 100644 --- a/clash_lib/src/proxy/transport/h2.rs +++ b/clash_lib/src/proxy/transport/h2.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Debug}; use bytes::{Bytes, BytesMut}; use futures::ready; @@ -64,6 +64,16 @@ pub struct Http2Stream { buffer: BytesMut, } +impl Debug for Http2Stream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Http2Stream") + .field("recv", &self.recv) + .field("send", &self.send) + .field("buffer", &self.buffer) + .finish() + } +} + impl Http2Stream { pub fn new(recv: RecvStream, send: SendStream) -> Self { Self { diff --git a/clash_lib/src/proxy/transport/websocket/websocket.rs b/clash_lib/src/proxy/transport/websocket/websocket.rs index 987e01cf0..eedc4ace0 100644 --- a/clash_lib/src/proxy/transport/websocket/websocket.rs +++ b/clash_lib/src/proxy/transport/websocket/websocket.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use std::{fmt::Debug, pin::Pin}; use bytes::{Buf, Bytes}; use futures::{ready, Sink, Stream}; @@ -15,6 +15,15 @@ pub struct WebsocketConn { read_buffer: Option, } +impl Debug for WebsocketConn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WebsocketConn") + .field("inner", &self.inner) + .field("read_buffer", &self.read_buffer) + .finish() + } +} + impl WebsocketConn { pub fn from_websocket(stream: WebSocketStream) -> Self { Self { diff --git a/clash_lib/src/proxy/transport/websocket/websocket_early_data.rs b/clash_lib/src/proxy/transport/websocket/websocket_early_data.rs index 71e31c397..ff469b0c3 100644 --- a/clash_lib/src/proxy/transport/websocket/websocket_early_data.rs +++ b/clash_lib/src/proxy/transport/websocket/websocket_early_data.rs @@ -1,5 +1,6 @@ use std::{ cmp, + fmt::Debug, pin::Pin, task::{Poll, Waker}, }; @@ -32,6 +33,21 @@ pub struct WebsocketEarlyDataConn { early_data_flushed: bool, } +impl Debug for WebsocketEarlyDataConn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WebsocketEarlyDataConn") + .field("stream", &self.stream) + .field("req", &self.req) + .field("early_waker", &self.early_waker) + .field("flush_waker", &self.flush_waker) + .field("ws_config", &self.ws_config) + .field("early_data_header_name", &self.early_data_header_name) + .field("early_data_len", &self.early_data_len) + .field("early_data_flushed", &self.early_data_flushed) + .finish() + } +} + impl WebsocketEarlyDataConn { pub fn new( stream: AnyStream,