├── .cargo └── config.toml ├── .gitattributes ├── .github ├── Dockerfile ├── PULL_REQUEST_TEMPLATE.md ├── cliff.toml ├── dependabot.yaml ├── email-blacklist.txt └── workflows │ ├── ci.yml │ ├── coverage.yml │ ├── email-check.yml │ └── spell-check.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── Makefile ├── NOTICE.md ├── README.md ├── _typos.toml ├── bench ├── README.md └── flamegraph.svg ├── clash ├── Cargo.toml ├── build.rs ├── src │ └── main.rs └── tests │ └── data │ ├── config │ ├── Country-asn.mmdb │ ├── Country.mmdb │ ├── GeoLite2-ASN.mmdb │ ├── config.test.yaml │ ├── dns.cert │ ├── dns.key │ ├── dns.yaml │ ├── empty.yaml │ ├── example.org-key.pem │ ├── example.org.pem │ ├── hysteria.json │ ├── hysteria2.yaml │ ├── listeners │ │ └── tunnel.yaml │ ├── public │ │ ├── CNAME │ │ ├── _headers │ │ ├── apple-touch-icon-precomposed.png │ │ ├── assets │ │ │ ├── Config.39d8d2ef.css │ │ │ ├── Config.c09e8dbe.js │ │ │ ├── Connections.e48eac36.js │ │ │ ├── Connections.fb8ea59b.css │ │ │ ├── Fab.a0a7e573.css │ │ │ ├── Fab.ef67ff10.js │ │ │ ├── Logs.4b8e75d1.css │ │ │ ├── Logs.ac990610.js │ │ │ ├── Proxies.16b46af4.js │ │ │ ├── Proxies.3fa3509d.css │ │ │ ├── Rules.70e6962f.js │ │ │ ├── Rules.e03c54a8.css │ │ │ ├── Select.1e55eba1.css │ │ │ ├── Select.6c389032.js │ │ │ ├── TextFitler.61537a57.js │ │ │ ├── TextFitler.b21c0577.css │ │ │ ├── chart-lib.a8ad03fd.js │ │ │ ├── chevron-down.dd238e96.js │ │ │ ├── debounce.c2d20996.js │ │ │ ├── en.fb34eaf7.js │ │ │ ├── index.171f553a.js │ │ │ ├── index.8bb012c6.js │ │ │ ├── index.92e2d967.js │ │ │ ├── index.b38debfc.css │ │ │ ├── index.esm.e4dd1508.js │ │ │ ├── inter-latin-400-normal.0364d368.woff2 │ │ │ ├── inter-latin-400-normal.3ea830d4.woff │ │ │ ├── inter-latin-800-normal.a51ac27d.woff2 │ │ │ ├── inter-latin-800-normal.d08d7178.woff │ │ │ ├── logs.43986220.js │ │ │ ├── play.7b1a5f99.js │ │ │ ├── roboto-mono-latin-400-normal.7295944e.woff2 │ │ │ ├── roboto-mono-latin-400-normal.dffdffa7.woff │ │ │ ├── useRemainingViewPortHeight.7395542b.js │ │ │ └── zh.9b79b7bf.js │ │ ├── index.html │ │ ├── manifest.webmanifest │ │ ├── registerSW.js │ │ ├── sw.js │ │ ├── yacd-128.png │ │ ├── yacd-64.png │ │ └── yacd.ico │ ├── rule-set-classical.yaml │ ├── rule-set.yaml │ ├── rules.yaml │ ├── shadowquic.yaml │ ├── simple.yaml │ ├── socks5.yaml │ ├── ss-loop.yaml │ ├── ss.json │ ├── ss.yaml │ ├── ssh │ │ ├── .ssh │ │ │ ├── authorized_keys │ │ │ ├── test_ed25519 │ │ │ └── test_rsa │ │ └── ssh_host_keys │ │ │ └── sshd_config │ ├── tor.yaml │ ├── tproxy.yaml │ ├── trojan-grpc.json │ ├── trojan-ws.json │ ├── tuic.json │ ├── tun.yaml │ ├── uot.yaml │ ├── vmess-grpc.json │ ├── vmess-http2.json │ ├── vmess-ws.json │ ├── wg.yaml │ └── wg_config │ │ ├── .donoteditthisfile │ │ ├── coredns │ │ └── Corefile │ │ ├── peer1 │ │ ├── peer1.conf │ │ ├── peer1.png │ │ ├── presharedkey-peer1 │ │ ├── privatekey-peer1 │ │ └── publickey-peer1 │ │ ├── server │ │ ├── privatekey-server │ │ └── publickey-server │ │ ├── templates │ │ ├── peer.conf │ │ └── server.conf │ │ └── wg_confs │ │ └── wg0.conf │ └── docker │ ├── docker-compose.yml │ ├── nginx │ └── nginx.conf │ ├── ss │ ├── Dockerfile │ └── config.json │ ├── v2ray │ ├── cert.pem │ ├── config.json │ └── key.pem │ └── wg │ └── wg0.conf ├── clash_doc ├── BUILD.bazel ├── Cargo.toml └── src │ └── lib.rs ├── clash_ffi ├── Cargo.toml ├── cbindgen.toml └── src │ └── lib.rs ├── clash_lib ├── .gitignore ├── Cargo.toml ├── build.rs ├── src │ ├── app │ │ ├── api │ │ │ ├── handlers │ │ │ │ ├── config.rs │ │ │ │ ├── connection.rs │ │ │ │ ├── dns.rs │ │ │ │ ├── hello.rs │ │ │ │ ├── log.rs │ │ │ │ ├── memory.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── provider.rs │ │ │ │ ├── proxy.rs │ │ │ │ ├── restart.rs │ │ │ │ ├── rule.rs │ │ │ │ ├── traffic.rs │ │ │ │ ├── utils.rs │ │ │ │ └── version.rs │ │ │ ├── middlewares │ │ │ │ ├── auth.rs │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ ├── dispatcher │ │ │ ├── dispatcher_impl.rs │ │ │ ├── mod.rs │ │ │ ├── statistics_manager.rs │ │ │ └── tracked.rs │ │ ├── dns │ │ │ ├── config.rs │ │ │ ├── dhcp.rs │ │ │ ├── dns_client.rs │ │ │ ├── fakeip │ │ │ │ ├── file_store.rs │ │ │ │ ├── mem_store.rs │ │ │ │ └── mod.rs │ │ │ ├── filters.rs │ │ │ ├── helper.rs │ │ │ ├── mod.rs │ │ │ ├── resolver │ │ │ │ ├── enhanced.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── system.rs │ │ │ │ └── system_static_crt.rs │ │ │ ├── runtime.rs │ │ │ └── server │ │ │ │ ├── handler.rs │ │ │ │ └── mod.rs │ │ ├── inbound │ │ │ ├── manager.rs │ │ │ ├── mod.rs │ │ │ └── network_listener.rs │ │ ├── logging.rs │ │ ├── mod.rs │ │ ├── net │ │ │ └── mod.rs │ │ ├── outbound │ │ │ ├── manager.rs │ │ │ ├── mod.rs │ │ │ └── utils.rs │ │ ├── profile │ │ │ └── mod.rs │ │ ├── remote_content_manager │ │ │ ├── healthcheck.rs │ │ │ ├── http_client.rs │ │ │ ├── mod.rs │ │ │ └── providers │ │ │ │ ├── fetcher.rs │ │ │ │ ├── file_vehicle.rs │ │ │ │ ├── http_vehicle.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── proxy_provider │ │ │ │ ├── mod.rs │ │ │ │ ├── plain_provider.rs │ │ │ │ └── proxy_set_provider.rs │ │ │ │ └── rule_provider │ │ │ │ ├── cidr_trie.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── mrs.rs │ │ │ │ └── provider.rs │ │ └── router │ │ │ ├── mod.rs │ │ │ └── rules │ │ │ ├── domain.rs │ │ │ ├── domain_keyword.rs │ │ │ ├── domain_regex.rs │ │ │ ├── domain_suffix.rs │ │ │ ├── final_.rs │ │ │ ├── geodata │ │ │ ├── attribute.rs │ │ │ ├── matcher_group.rs │ │ │ ├── mod.rs │ │ │ └── str_matcher.rs │ │ │ ├── geoip.rs │ │ │ ├── ipcidr.rs │ │ │ ├── mod.rs │ │ │ ├── port.rs │ │ │ ├── process.rs │ │ │ └── ruleset.rs │ ├── common │ │ ├── auth.rs │ │ ├── crypto.rs │ │ ├── defer.rs │ │ ├── errors.rs │ │ ├── geodata │ │ │ ├── geodata.proto │ │ │ └── mod.rs │ │ ├── http │ │ │ ├── client.rs │ │ │ ├── hyper │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ ├── io │ │ │ ├── mod.rs │ │ │ └── splice.rs │ │ ├── mmdb.rs │ │ ├── mod.rs │ │ ├── succinct_set.rs │ │ ├── timed_future.rs │ │ ├── tls.rs │ │ ├── trie.rs │ │ └── utils.rs │ ├── config │ │ ├── def.rs │ │ ├── internal │ │ │ ├── config.rs │ │ │ ├── convert │ │ │ │ ├── general.rs │ │ │ │ ├── listener.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── proxy_group.rs │ │ │ │ ├── rule_provider.rs │ │ │ │ └── tun.rs │ │ │ ├── listener.rs │ │ │ ├── mod.rs │ │ │ ├── proxy.rs │ │ │ └── rule.rs │ │ ├── mod.rs │ │ └── utils.rs │ ├── lib.rs │ ├── proxy │ │ ├── common.rs │ │ ├── converters │ │ │ ├── hysteria2.rs │ │ │ ├── mod.rs │ │ │ ├── shadowquic.rs │ │ │ ├── shadowsocks.rs │ │ │ ├── socks5.rs │ │ │ ├── ssh.rs │ │ │ ├── tor.rs │ │ │ ├── trojan.rs │ │ │ ├── tuic.rs │ │ │ ├── utils.rs │ │ │ ├── vmess.rs │ │ │ └── wireguard.rs │ │ ├── datagram.rs │ │ ├── direct │ │ │ └── mod.rs │ │ ├── group │ │ │ ├── fallback │ │ │ │ └── mod.rs │ │ │ ├── loadbalance │ │ │ │ ├── helpers.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── relay │ │ │ │ └── mod.rs │ │ │ ├── selector │ │ │ │ └── mod.rs │ │ │ ├── smart │ │ │ │ ├── mod.rs │ │ │ │ ├── penalty.rs │ │ │ │ ├── state.rs │ │ │ │ └── stats.rs │ │ │ └── urltest │ │ │ │ └── mod.rs │ │ ├── http │ │ │ ├── inbound │ │ │ │ ├── auth.rs │ │ │ │ ├── connector.rs │ │ │ │ ├── mod.rs │ │ │ │ └── proxy.rs │ │ │ └── mod.rs │ │ ├── hysteria2 │ │ │ ├── codec.rs │ │ │ ├── congestion.rs │ │ │ ├── datagram.rs │ │ │ ├── mod.rs │ │ │ ├── salamander.rs │ │ │ └── udp_hop.rs │ │ ├── inbound.rs │ │ ├── mixed │ │ │ └── mod.rs │ │ ├── mocks.rs │ │ ├── mod.rs │ │ ├── options.rs │ │ ├── reject │ │ │ └── mod.rs │ │ ├── shadowquic │ │ │ ├── compat.rs │ │ │ └── mod.rs │ │ ├── shadowsocks │ │ │ ├── datagram.rs │ │ │ ├── mod.rs │ │ │ └── stream.rs │ │ ├── socks │ │ │ ├── inbound │ │ │ │ ├── datagram.rs │ │ │ │ ├── mod.rs │ │ │ │ └── stream.rs │ │ │ ├── mod.rs │ │ │ ├── outbound │ │ │ │ ├── datagram.rs │ │ │ │ └── mod.rs │ │ │ └── socks5.rs │ │ ├── ssh │ │ │ ├── auth.rs │ │ │ ├── connector.rs │ │ │ └── mod.rs │ │ ├── tor │ │ │ ├── mod.rs │ │ │ └── stream.rs │ │ ├── tproxy │ │ │ ├── iptables.sh │ │ │ └── mod.rs │ │ ├── transport │ │ │ ├── grpc.rs │ │ │ ├── h2.rs │ │ │ ├── mod.rs │ │ │ ├── shadow_tls │ │ │ │ ├── mod.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── stream.rs │ │ │ │ └── utils.rs │ │ │ ├── simple_obfs │ │ │ │ ├── http.rs │ │ │ │ ├── mod.rs │ │ │ │ └── tls.rs │ │ │ ├── sip003 │ │ │ │ └── mod.rs │ │ │ ├── tls.rs │ │ │ ├── v2ray │ │ │ │ ├── mod.rs │ │ │ │ └── websocket.rs │ │ │ └── ws │ │ │ │ ├── mod.rs │ │ │ │ ├── websocket.rs │ │ │ │ └── websocket_early_data.rs │ │ ├── trojan │ │ │ ├── datagram.rs │ │ │ └── mod.rs │ │ ├── tuic │ │ │ ├── compat.rs │ │ │ ├── handle_stream.rs │ │ │ ├── handle_task.rs │ │ │ ├── mod.rs │ │ │ └── types.rs │ │ ├── tun │ │ │ ├── datagram.rs │ │ │ ├── inbound.rs │ │ │ ├── mod.rs │ │ │ ├── routes │ │ │ │ ├── linux.rs │ │ │ │ ├── macos.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── other.rs │ │ │ │ └── windows.rs │ │ │ └── stream.rs │ │ ├── tunnel │ │ │ └── mod.rs │ │ ├── utils │ │ │ ├── mod.rs │ │ │ ├── platform │ │ │ │ ├── apple.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── unix.rs │ │ │ │ └── win.rs │ │ │ ├── provider_helper.rs │ │ │ ├── proxy_connector.rs │ │ │ ├── socket_helpers.rs │ │ │ └── test_utils │ │ │ │ ├── docker_utils │ │ │ │ ├── config_helper.rs │ │ │ │ ├── consts.rs │ │ │ │ ├── docker_runner.rs │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ └── noop.rs │ │ ├── vmess │ │ │ ├── mod.rs │ │ │ └── vmess_impl │ │ │ │ ├── cipher.rs │ │ │ │ ├── client.rs │ │ │ │ ├── datagram.rs │ │ │ │ ├── header.rs │ │ │ │ ├── http.rs │ │ │ │ ├── kdf.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── stream.rs │ │ │ │ └── user.rs │ │ └── wg │ │ │ ├── device.rs │ │ │ ├── events.rs │ │ │ ├── keys.rs │ │ │ ├── mod.rs │ │ │ ├── ports.rs │ │ │ ├── stack │ │ │ ├── mod.rs │ │ │ ├── tcp.rs │ │ │ └── udp.rs │ │ │ └── wireguard.rs │ └── session.rs └── tests │ └── data │ └── Country.mmdb ├── docs ├── .lock ├── clash_doc │ ├── all.html │ ├── index.html │ ├── sidebar-items.js │ ├── struct.ClashConfigDef.html │ └── struct.ClashDNSConfigDef.html ├── crates.js ├── help.html ├── index.html ├── search-index.js ├── settings.html ├── source-files.js ├── src │ └── clash_doc │ │ └── lib.rs.html └── static.files │ ├── COPYRIGHT-23e9bde6c69aea69.txt │ ├── FiraSans-LICENSE-db4b642586e02d97.txt │ ├── FiraSans-Medium-8f9a781e4970d388.woff2 │ ├── FiraSans-Regular-018c141bf0843ffd.woff2 │ ├── LICENSE-APACHE-b91fa81cba47b86a.txt │ ├── LICENSE-MIT-65090b722b3f6c56.txt │ ├── NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2 │ ├── NanumBarunGothic-LICENSE-18c5adf4b52b4041.txt │ ├── SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2 │ ├── SourceCodePro-LICENSE-d180d465a756484a.txt │ ├── SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2 │ ├── SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2 │ ├── SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2 │ ├── SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2 │ ├── SourceSerif4-LICENSE-3bb119e13b1258b7.md │ ├── SourceSerif4-Regular-46f98efaafac5295.ttf.woff2 │ ├── ayu-614652228113ac93.css │ ├── clipboard-7571035ce49a181d.svg │ ├── dark-1097f8e92a01e3cf.css │ ├── favicon-16x16-8b506e7a72182f1c.png │ ├── favicon-2c020d218678b618.svg │ ├── favicon-32x32-422f7d1d52889060.png │ ├── light-0f8c037637f9eb3e.css │ ├── main-f61008743c98d196.js │ ├── normalize-76eba96aa4d2e634.css │ ├── noscript-13285aec31fa243e.css │ ├── rust-logo-151179464ae7ed46.svg │ ├── rustdoc-ba5701c5741a7b69.css │ ├── scrape-examples-ef1e698c1d417c0c.js │ ├── search-e077946657036a58.js │ ├── settings-298e1ea74db45b39.js │ ├── settings-7bfb4c59cc6bc502.css │ ├── source-script-905937fbbdc8e9ea.js │ ├── storage-62ce34ea385b278a.js │ └── wheel-7b819b6101059cd0.svg ├── rust-toolchain.toml ├── rustfmt.toml └── scripts ├── build_apple.sh ├── check_socks5.py ├── logs.py └── requirements.txt /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target-dir = "target" 3 | rustflags = ["--cfg", "tokio_unstable"] 4 | 5 | [env] 6 | RUST_LOG = { value = "clash=trace" } 7 | SENTRY_DSN = { value = "PLEASE DEFINE AT COMPILE TIME" } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/.gitattributes -------------------------------------------------------------------------------- /.github/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | # Define an ARG for the target architecture 3 | ARG TARGETARCH 4 | COPY ./clash-rs/clash-${TARGETARCH} /usr/bin/clash 5 | # The yq library installed here is used to rewrite the config.yaml configuration file for clash, merge it, and other related operations. 6 | RUN apk update && apk add --no-cache -f yq && mkdir -p /root/.config/clash/ && chmod +x /usr/bin/clash 7 | WORKDIR /root 8 | ENTRYPOINT [ "/usr/bin/clash" ] 9 | CMD [ "-d", "/root/.config/clash/" ] -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### 🤔 This is a ... 10 | 11 | - [ ] New feature 12 | - [ ] Bug fix 13 | - [ ] Performance optimization 14 | - [ ] Enhancement feature 15 | - [ ] Refactoring 16 | - [ ] Code style optimization 17 | - [ ] Test Case 18 | - [ ] Branch merge 19 | - [ ] Workflow 20 | - [ ] Other (about what?) 21 | 22 | ### 🔗 Related issue link 23 | 24 | 28 | 29 | ### 💡 Background and solution 30 | 31 | 36 | 37 | ### 📝 Changelog 38 | 39 | 42 | 43 | 44 | ### ☑️ Self-Check before Merge 45 | 46 | ⚠️ Please check all items below before requesting a reviewing. ⚠️ 47 | 48 | - [ ] Doc is updated/provided or not needed 49 | - [ ] Changelog is provided or not needed 50 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | groups: 8 | rust-dependencies: 9 | patterns: 10 | - "*" 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: weekly 15 | groups: 16 | actions-dependencies: 17 | patterns: 18 | - "*" 19 | 20 | -------------------------------------------------------------------------------- /.github/email-blacklist.txt: -------------------------------------------------------------------------------- 1 | *@qq.com 2 | *@foxmail.com 3 | *@126.com 4 | *@163.com 5 | *@sina.com 6 | MX-RECORD,mxbiz1.qq.com 7 | MX-RECORD,mx1.qiye.aliyun.com 8 | MX-RECORD,mx.ym.163.com 9 | MX-RECORD,mx.huaweicloud.com 10 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | branches: ["master"] 7 | pull_request: 8 | branches: ["master"] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | compile: 16 | name: Coverage 17 | runs-on: 'ubuntu-latest' 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: true 22 | 23 | - uses: actions/cache@v4 24 | with: 25 | path: | 26 | ~/.cargo/registry 27 | ~/.cargo/git 28 | ~/.cargo/bin/ 29 | ~/.cargo/registry/index/ 30 | ~/.cargo/registry/cache/ 31 | ~/.cargo/git/db/ 32 | key: coverage-${{ hashFiles('**/Cargo.toml') }} 33 | 34 | - name: Install cargo-llvm-cov 35 | uses: taiki-e/install-action@cargo-llvm-cov 36 | - name: Install Protoc 37 | uses: arduino/setup-protoc@v3 38 | with: 39 | version: "23.x" 40 | repo-token: ${{ secrets.GITHUB_TOKEN }} 41 | 42 | - name: Cargo test and coverage 43 | uses: clechasseur/rs-cargo@v3 44 | with: 45 | tool: cross 46 | command: 'llvm-cov' 47 | args: --workspace --exclude clash_ffi -F "plus" --codecov --output-path codecov.json 48 | env: 49 | CROSS_CONTAINER_OPTS: "--network host" 50 | CLASH_DOCKER_TEST: 'true' 51 | 52 | - name: Upload coverage to Codecov 53 | uses: codecov/codecov-action@v5 54 | if: ${{ !cancelled() }} 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | files: codecov.json 58 | fail_ci_if_error: true 59 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [ pull_request ] 7 | 8 | env: 9 | CLICOLOR: 1 10 | 11 | jobs: 12 | spelling: 13 | name: Spell Check with Typos 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Actions Repository 17 | uses: actions/checkout@v4 18 | - name: Spell Check Repo 19 | uses: crate-ci/typos@v1.33.1 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | **/*.rs.bk 4 | *.pdb 5 | .idea/ 6 | .vscode/ 7 | .config/ 8 | .venv/ 9 | /bazel-* 10 | /output/ 11 | rust-project.json 12 | /build/ 13 | 14 | # don't check in this real config 15 | ignore*.yaml 16 | config.yaml 17 | cache.db 18 | Country.mmdb 19 | ruleset/ 20 | geosite.dat 21 | 22 | # for NixOS direnv 23 | .envrc 24 | shell.nix 25 | 26 | # macOS 27 | .DS_Store 28 | 29 | codecov.json 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/.gitmodules -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | - Try to use this project and report issues 2 | - Request any features that you'd like to see 3 | - Contributing to coding is more than welcome 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "clash", 6 | "clash_lib", 7 | "clash_doc", 8 | "clash_ffi", 9 | ] 10 | 11 | 12 | [workspace.package] 13 | version = "0.7.8" 14 | repository = "https://github.com/Watfaq/clash-rs.git" 15 | edition = "2024" 16 | authors = ["https://github.com/Watfaq/clash-rs/graphs/contributors"] 17 | homepage = "https://github.com/watfaq/clash-rs" 18 | 19 | [profile.release] 20 | opt-level = "s" 21 | codegen-units = 1 22 | lto = "thin" 23 | strip = true 24 | debug = 2 25 | panic = "abort" 26 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | pre-build = [ 3 | "apt update", 4 | "apt install -y protobuf-compiler" 5 | ] 6 | 7 | [build.env] 8 | volumes = ["/var/run/docker.sock=/var/run/docker.sock", "/tmp=/tmp"] # Docker in docker 9 | passthrough = ["CLASH_GIT_REF", "CLASH_GIT_SHA", "RUSTFLAGS", "RUST_LOG", "CLASH_DOCKER_TEST", "SENTRY_DSN"] 10 | 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs 2 | docs: 3 | @rm -rf ./docs 4 | @cargo doc -p clash_doc --no-deps 5 | @echo "" > target/doc/index.html 6 | @cp -r target/doc ./docs 7 | 8 | test-no-docker: 9 | CLASH_RS_CI=true cargo test --all --all-features 10 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | When compiled with the following features, distributed with `GPL3` license 2 | - `tuic` 3 | 4 | Otherwise distributed with `Apache 2.0` license 5 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | locale = "en" 3 | 4 | [default.extend-words] 5 | # Session:typ 6 | typ = "typ" 7 | 8 | [files] 9 | extend-exclude = [ 10 | "docs", 11 | "bench", 12 | "clash/tests/data/config/public", 13 | "clash/tests/data/config/*.cert", 14 | ] 15 | 16 | -------------------------------------------------------------------------------- /clash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clash-rs" 3 | repository = { workspace = true } 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | homepage = { workspace = true } 7 | authors = { workspace = true } 8 | 9 | [features] 10 | default = ["standard"] 11 | standard = ["shadowsocks", "tuic", "ssh", "clash_lib/zero_copy", "shadowquic"] 12 | plus = ["standard", "onion"] 13 | perf = ["plus", "jemallocator"] 14 | 15 | android = ["shadowsocks", "tuic"] # Android build failed with libc 16 | bsd = ["shadowsocks", "tuic"] 17 | 18 | shadowsocks = ["clash_lib/shadowsocks"] 19 | ssh = ["clash_lib/ssh"] 20 | tuic = ["clash_lib/tuic"] 21 | onion = ["clash_lib/onion"] 22 | shadowquic = ["clash_lib/shadowquic"] 23 | 24 | bench = ["clash_lib/bench"] 25 | dhat-heap = ["dep:dhat"] 26 | tokio-console = ["clash_lib/tokio-console"] 27 | jemallocator = ["dep:tikv-jemallocator"] 28 | 29 | [dependencies] 30 | clap = { version = "4", features = ["derive"] } 31 | 32 | clash_lib = { path = "../clash_lib", default-features = false } 33 | 34 | dhat = { version = "0.3", optional = true } 35 | tikv-jemallocator = { version = "0.6", optional = true } 36 | 37 | sentry = { version = "0.38", default-features = false, features = ["backtrace", "contexts", "panic", "reqwest", "rustls"] } 38 | human-panic = "2.0" 39 | -------------------------------------------------------------------------------- /clash/build.rs: -------------------------------------------------------------------------------- 1 | #![feature(let_chains)] 2 | fn main() { 3 | let vars = ["CLASH_GIT_REF", "CLASH_GIT_SHA"]; 4 | for var in vars { 5 | println!("cargo:rerun-if-env-changed={var}"); 6 | } 7 | 8 | let version = if let Some("refs/heads/master") = option_env!("CLASH_GIT_REF") 9 | && let Some(sha) = option_env!("CLASH_GIT_SHA") 10 | { 11 | let short_sha = &sha[..7]; 12 | // Nightly release below 13 | format!("{}-alpha+sha.{short_sha}", env!("CARGO_PKG_VERSION")) 14 | } else { 15 | env!("CARGO_PKG_VERSION").into() 16 | }; 17 | println!("cargo:rustc-env=CLASH_VERSION_OVERRIDE={version}"); 18 | } 19 | -------------------------------------------------------------------------------- /clash/tests/data/config/Country-asn.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/Country-asn.mmdb -------------------------------------------------------------------------------- /clash/tests/data/config/Country.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/Country.mmdb -------------------------------------------------------------------------------- /clash/tests/data/config/GeoLite2-ASN.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/GeoLite2-ASN.mmdb -------------------------------------------------------------------------------- /clash/tests/data/config/dns.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDITCCAgmgAwIBAgIQOp++4lLpVGLWkaGuRrrP2zANBgkqhkiG9w0BAQsFADAS 3 | MRAwDgYDVQQKEwdBY21lIENvMB4XDTIzMDkwMjA4MzIyM1oXDTI0MDkwMTA4MzIy 4 | M1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBAPS+pfYexKHq50myjnY6kAZ1uLDmgR/Vy9y7rPcrkqci25ClCnuUiUgY 6 | ABhrdq16oW2wOymc9A56YH9X99UNX6E+VmckCP8v9xcPV4/Zoc+KIeuJHRUBHFMK 7 | ieiWe560XcRCTP7OudUZ+iSHbjhaxZJCEfFaE8aSZXmiLKUETZ7z2lAm75fRCBaR 8 | Vy+jewy04F1BKg4VlnrZnVHzQCcBHZEpsAz4YpbNHMYf9sYRrh+T2j9Zya5tUklI 9 | LzEobWXUzjhrkcBXSVC5dLa9sxz7eSeg0dY0SkP7O5pelr8LaXnBudAk5B0ByqFD 10 | s83Z1AZMkv7vL2L8AGA/X1PP2UV3BrECAwEAAaNzMHEwDgYDVR0PAQH/BAQDAgKk 11 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE 12 | FIiHKw+Z9rUjV6pbqcDl+teSTLxoMBoGA1UdEQQTMBGCD2Rucy5leGFtcGxlLmNv 13 | bTANBgkqhkiG9w0BAQsFAAOCAQEACTYIij1QqPhke3aI6HVLuXy75YHjxic0cIcX 14 | 7u41WSnZWOrnsBYAFPfjwH7sW6dX+5twUludSCSQ9Xyhl/tdQ0AKWJJNZ7irArS9 15 | kz2rU4u7YjtR4tuuRB2t+8UEcGA/m0EPhQfFbZedAy/Y2oc+RodwlqibVB/WCMOQ 16 | BL4HS1wFaYZw9WhXk3nzb+wjBhvyEQkI9oeMqVYZLN/9kIY9QuGtDg5onFrVSgjZ 17 | qiR7EfdICe8ogM6IiemQJfZ5SeWkoLpuRlaeVhqFFaFFeJ6cMTJ+Jluh6a3DGP91 18 | aRaPVO7r8gPq4mACua0HQcBfmH4VKS3hsQHdWDivRUT7xkZ/6g== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /clash/tests/data/config/dns.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0vqX2HsSh6udJ 3 | so52OpAGdbiw5oEf1cvcu6z3K5KnItuQpQp7lIlIGAAYa3ateqFtsDspnPQOemB/ 4 | V/fVDV+hPlZnJAj/L/cXD1eP2aHPiiHriR0VARxTConolnuetF3EQkz+zrnVGfok 5 | h244WsWSQhHxWhPGkmV5oiylBE2e89pQJu+X0QgWkVcvo3sMtOBdQSoOFZZ62Z1R 6 | 80AnAR2RKbAM+GKWzRzGH/bGEa4fk9o/WcmubVJJSC8xKG1l1M44a5HAV0lQuXS2 7 | vbMc+3knoNHWNEpD+zuaXpa/C2l5wbnQJOQdAcqhQ7PN2dQGTJL+7y9i/ABgP19T 8 | z9lFdwaxAgMBAAECggEAaMFNYcoLmc5kjsvJZFtumAU9NyKCNDEbX/BIeUcCL12h 9 | IwkxMnICTIRRTiJ5Gom5nKxotkgCwkupD/iEEIH345k9/EmVPDy4gvtDHEQnmSBj 10 | ol/+vaXLDNQe8Rmv8d77n2xNbmbnbYn/4jDBgYeAtzhmW6qVelHg8y3x8/OikZyy 11 | EB+ORiH/p4XN+3xBuEnFwBjQSHlayYVL8E68rp2wcKIeB2wo27Noh+VNqc2jks/l 12 | h9V+cydCOHY2OQ0iepZwPmsVVjEObtckksBTFWgpc5ZH6Ctq1YjtTpcOey9FRzhZ 13 | fuK9cj7bO5uW1duocgMPGsgiYMdvGyOCmM4tgp4lwQKBgQD+SXkzutxu0577g3Td 14 | 8BWYvksM6O3Lq8ZHKQtcxcNpp9S/fQ134IMErlykS1FJ5290KkHwQ4tojiOhtYQo 15 | GOlmbS7USR2puhNzEWxmhi0CIyyWyRDECrVZVnLfcVxZrxpoh4LjZFA6nVRhZipN 16 | oMig1zDhVmhvUTrTxVygFp9/aQKBgQD2ZLgGTK0kZ4npsUEL/ZantlZwEpY8S3TK 17 | muLlcGx3EFwPrX558xhYSwkh/afNTcRekJkoBA9SXaioZNKlOD/XyIFq0OeurygB 18 | gC8RdBs3xAjNHyj9qxajhuBqxxxTa/HmUnWqH2zDv20DcyvLTXjpRuFBxBj77ka9 19 | eVyGBxmsCQKBgQCC2tJpIWagDXyJl2tDbnHeqUY7vX3pSlr9cYysUASwUTJ02+hb 20 | YQhrF0MLNMr/Cf7bu4c1Gb0ar9J8O8lnTPKGx/bKPVnrZprtovCyjaeJqwoeChf7 21 | mjsaXxc8DrzkVex0EA/17kAu+ZlbidSJIA0+X56CxxF0/0sTgUOaCipHyQKBgQDl 22 | Bie7y0fhH9CkhRtWPufrimP8FnrJHsY3kRK4e/CGF5HLDNQUHK8TWuPpUXLJNbEC 23 | yVtjQ6rOP7qGk/jslEVbmMca94Vy7OK9yl111rt58WDQ8VbTu1T2uWceOWeN7zdR 24 | hHJUqJMbvHJjE4mwlpl+FGFLFTC38/qTIhyrhCwLqQKBgQDz67mt3XyD5HUT/GYn 25 | A91ZGjWfh3YPXYkjOOjw1MWqeO7LQi6A8uWkfRFPWn/h3gECqItrWFogJixRyL+G 26 | TWQ7SWmJctHD8YjU3E6tlPSWQhD2U75pZWtjpGAIYSdvS5V9Q+95jaKKhsYneThB 27 | SNEnosgWOR5eg9c0FznaDn6nRw== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /clash/tests/data/config/dns.yaml: -------------------------------------------------------------------------------- 1 | mixed-port: 7890 2 | mode: rule 3 | log-level: trace 4 | 5 | tun: 6 | enable: true 7 | route-all: true 8 | device-id: dev://utun1989 9 | dns-hijack: true 10 | 11 | dns: 12 | enable: true 13 | enhanced-mode: fake-ip 14 | listen: 15 | udp: 127.0.0.1:53 16 | fake-ip-range: 198.18.0.1/16 17 | nameserver: 18 | - 223.5.5.5 19 | - 180.184.1.1 20 | 21 | rules: 22 | - MATCH,DIRECT 23 | -------------------------------------------------------------------------------- /clash/tests/data/config/empty.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8888 3 | socks-port: 8889 4 | mixed-port: 8899 5 | 6 | dns: 7 | enable: true 8 | listen: 127.0.0.1:53533 9 | # ipv6: false # when the false, response to AAAA questions will be empty 10 | 11 | # These nameservers are used to resolve the DNS nameserver hostnames below. 12 | # Specify IP addresses only 13 | default-nameserver: 14 | - 114.114.114.114 15 | - 8.8.8.8 16 | enhanced-mode: fake-ip # or fake-ip 17 | fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR 18 | # use-hosts: true # lookup hosts and return IP record 19 | 20 | # Hostnames in this list will not be resolved with fake IPs 21 | # i.e. questions to these domain names will always be answered with their 22 | # real IP addresses 23 | # fake-ip-filter: 24 | # - '*.lan' 25 | # - localhost.ptlogin2.qq.com 26 | 27 | # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. 28 | # All DNS questions are sent directly to the nameserver, without proxies 29 | # involved. Clash answers the DNS question with the first result gathered. 30 | nameserver: 31 | - 114.114.114.114 # default value 32 | - 8.8.8.8 # default value 33 | - tls://dns.google:853 # DNS over TLS 34 | - https://1.1.1.1/dns-query # DNS over HTTPS 35 | 36 | allow-lan: true 37 | mode: rule 38 | log-level: debug 39 | external-controller: 127.0.0.1:6170 40 | experimental: 41 | ignore-resolve-fail: true 42 | 43 | rules: 44 | - MATCH, DIRECT 45 | -------------------------------------------------------------------------------- /clash/tests/data/config/example.org-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5 3 | 5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo 4 | PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE 5 | sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R 6 | i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5 7 | LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge 8 | gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+ 9 | y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO 10 | jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z 11 | ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv 12 | H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG 13 | o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ 14 | CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49 15 | aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33 16 | bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7 17 | Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh 18 | ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO 19 | pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT 20 | 4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi 21 | GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA 22 | vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB 23 | fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z 24 | zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X 25 | DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28 26 | 9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP 27 | XCar+uxMBXI1zbXqd9QdEwy4Ig== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /clash/tests/data/config/example.org.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB 3 | hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh 4 | bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl 5 | cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz 6 | MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl 7 | bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy 8 | by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 9 | ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8 10 | 3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI 11 | YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b 12 | IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo 13 | UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2 14 | I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG 15 | A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln 16 | F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP 17 | TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa 18 | Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1 19 | Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz 20 | Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG 21 | WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6 22 | AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6 23 | gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS 24 | tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /clash/tests/data/config/hysteria.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": ":10002", 3 | "tls":{ 4 | "cert": "/home/ubuntu/my.crt", 5 | "key": "/home/ubuntu/my.key" 6 | }, 7 | "obfs": { 8 | "type": "salamander", 9 | "salamander": { 10 | "password": "beauty will save the world" 11 | } 12 | }, 13 | "up_mbps": 100, 14 | "down_mbps": 100, 15 | "auth": { 16 | "type": "password", 17 | "password": "passwd" 18 | } 19 | } -------------------------------------------------------------------------------- /clash/tests/data/config/hysteria2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8891 3 | socks-port: 8889 4 | mixed-port: 8888 5 | 6 | 7 | dns: 8 | enable: true 9 | listen: 127.0.0.1:53533 10 | # ipv6: false # when the false, response to AAAA questions will be empty 11 | 12 | # These nameservers are used to resolve the DNS nameserver hostnames below. 13 | # Specify IP addresses only 14 | default-nameserver: 15 | - 114.114.114.114 16 | - 8.8.8.8 17 | enhanced-mode: fake-ip # or fake-ip 18 | fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR 19 | # use-hosts: true # lookup hosts and return IP record 20 | 21 | # Hostnames in this list will not be resolved with fake IPs 22 | # i.e. questions to these domain names will always be answered with their 23 | # real IP addresses 24 | # fake-ip-filter: 25 | # - '*.lan' 26 | # - localhost.ptlogin2.qq.com 27 | 28 | # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. 29 | # All DNS questions are sent directly to the nameserver, without proxies 30 | # involved. Clash answers the DNS question with the first result gathered. 31 | nameserver: 32 | - 114.114.114.114 # default value 33 | - 8.8.8.8 # default value 34 | # - tls://dns.google:853 # DNS over TLS 35 | # - https://1.1.1.1/dns-query # DNS over HTTPS 36 | # - dhcp://en0 # dns from dhcp 37 | 38 | allow-lan: true 39 | mode: rule 40 | log-level: debug 41 | external-controller: 127.0.0.1:6170 42 | experimental: 43 | ignore-resolve-fail: true 44 | 45 | proxies: 46 | - name: "local" 47 | type: hysteria2 48 | server: 127.0.0.1 49 | port: 10086 50 | password: passwd 51 | sni: example.com 52 | skip-cert-verify: true 53 | obfs: salamander 54 | obfs-password: "passwd" 55 | 56 | rules: 57 | - MATCH, local 58 | -------------------------------------------------------------------------------- /clash/tests/data/config/listeners/tunnel.yaml: -------------------------------------------------------------------------------- 1 | port: 8080 2 | socks-port: 8081 3 | log-level: trace 4 | listeners: 5 | - name: tunnel-in 6 | type: tunnel 7 | port: 15201 8 | listen: 127.0.0.1 9 | network: [tcp, udp] 10 | target: 127.0.0.1:5201 -------------------------------------------------------------------------------- /clash/tests/data/config/public/CNAME: -------------------------------------------------------------------------------- 1 | yacd.haishan.me 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/_headers: -------------------------------------------------------------------------------- 1 | # for netlify hosting 2 | # https://docs.netlify.com/routing/headers/#syntax-for-the-headers-file 3 | 4 | /* 5 | X-Frame-Options: DENY 6 | X-XSS-Protection: 1; mode=block 7 | X-Content-Type-Options: nosniff 8 | Referrer-Policy: same-origin 9 | /*.css 10 | Cache-Control: public, max-age=31536000, immutable 11 | /*.js 12 | Cache-Control: public, max-age=31536000, immutable 13 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/Config.39d8d2ef.css: -------------------------------------------------------------------------------- 1 | ._root_v2s4h_1,._section_v2s4h_2{display:grid;grid-template-columns:repeat(auto-fill,minmax(345px,1fr));max-width:900px;gap:5px}@media screen and (min-width: 30em){._root_v2s4h_1,._section_v2s4h_2{gap:15px}}._root_v2s4h_1,._section_v2s4h_2{padding:6px 15px 10px}@media screen and (min-width: 30em){._root_v2s4h_1,._section_v2s4h_2{padding:10px 40px 15px}}._wrapSwitch_v2s4h_26{height:40px;display:flex;align-items:center}._sep_v2s4h_32{max-width:900px;padding:0 15px}@media screen and (min-width: 30em){._sep_v2s4h_32{padding:0 40px}}._sep_v2s4h_32>div{border-top:1px dashed #373737}._label_v2s4h_45{padding:11px 0}._fieldset_1ghjp_1{margin:0;padding:0;border:0;display:flex;flex-wrap:wrap}._input_1ghjp_9+._cnt_1ghjp_9{border:1px solid transparent;border-radius:8px;cursor:pointer;margin-right:5px;margin-bottom:5px}._input_1ghjp_9:focus+._cnt_1ghjp_9{border-color:#387cec}._input_1ghjp_9:checked+._cnt_1ghjp_9{border-color:#387cec} 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/Fab.ef67ff10.js: -------------------------------------------------------------------------------- 1 | import{j as e,b,i as y,r as l}from"./index.171f553a.js";const F="_spining_4i8sg_1",M="_spining_keyframes_4i8sg_1",j={spining:F,spining_keyframes:M},{useState:v}=y;function B({children:s}){return e("span",{className:j.spining,children:s})}const H={right:10,bottom:10},L=({children:s,...n})=>e("button",{type:"button",...n,className:"rtf--ab",children:s}),E=({children:s,...n})=>e("button",{type:"button",className:"rtf--mb",...n,children:s}),O={bottom:24,right:24},R=({event:s="hover",style:n=O,alwaysShowTitle:o=!1,children:f,icon:g,mainButtonStyles:h,onClick:p,text:d,..._})=>{const[a,r]=v(!1),c=o||!a,u=()=>r(!0),m=()=>r(!1),x=()=>s==="hover"&&u(),k=()=>s==="hover"&&m(),N=t=>p?p(t):(t.persist(),s==="click"?a?m():u():null),$=(t,i)=>{t.persist(),r(!1),setTimeout(()=>{i(t)},1)},C=()=>l.exports.Children.map(f,(t,i)=>l.exports.isValidElement(t)?b("li",{className:`rtf--ab__c ${"top"in n?"top":""}`,children:[l.exports.cloneElement(t,{"data-testid":`action-button-${i}`,"aria-label":t.props.text||`Menu button ${i+1}`,"aria-hidden":c,tabIndex:a?0:-1,...t.props,onClick:I=>{t.props.onClick&&$(I,t.props.onClick)}}),t.props.text&&e("span",{className:`${"right"in n?"right":""} ${o?"always-show":""}`,"aria-hidden":c,children:t.props.text})]}):null);return e("ul",{onMouseEnter:x,onMouseLeave:k,className:`rtf ${a?"open":"closed"}`,"data-testid":"fab",style:n,..._,children:b("li",{className:"rtf--mb__c",children:[e(E,{onClick:N,style:h,"data-testid":"main-button",role:"button","aria-label":"Floating menu",tabIndex:0,children:g}),d&&e("span",{className:`${"right"in n?"right":""} ${o?"always-show":""}`,"aria-hidden":c,children:d}),e("ul",{children:C()})]})})};export{L as A,R as F,B as I,H as p}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/Logs.4b8e75d1.css: -------------------------------------------------------------------------------- 1 | ._RuleSearch_1oz2t_1{padding:0 40px 5px}._RuleSearchContainer_1oz2t_5{position:relative;height:40px}._inputWrapper_1oz2t_10{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);left:0;width:100%}._input_1oz2t_10{-webkit-appearance:none;background-color:var(--color-input-bg);background-image:none;border-radius:20px;border:1px solid var(--color-input-border);box-sizing:border-box;color:#c1c1c1;display:inline-block;font-size:inherit;height:40px;outline:none;padding:0 15px 0 35px;transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}._iconWrapper_1oz2t_35{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);left:10px;display:flex;justify-content:center;align-items:center}._logMeta_7a1x3_1{display:flex;align-items:center;flex-wrap:wrap;font-size:.9em}._logType_7a1x3_8{color:#eee;flex-shrink:0;text-align:center;width:66px;border-radius:100px;padding:3px 5px;margin:0 8px}._logTime_7a1x3_18{flex-shrink:0;color:#999;font-size:14px}._logText_7a1x3_24{flex-shrink:0;display:flex;font-family:Roboto Mono,Menlo,monospace;align-items:center;padding:8px 0;width:100%;white-space:pre;overflow:auto}._logsWrapper_7a1x3_37{margin:0;padding:0;color:var(--color-text)}._logsWrapper_7a1x3_37 .log{padding:10px 40px;background:var(--color-background)}._logsWrapper_7a1x3_37 .log.even{background:var(--color-background)}._logPlaceholder_7a1x3_51{display:flex;flex-direction:column;align-items:center;justify-content:center;color:#2d2d30}._logPlaceholder_7a1x3_51 div:nth-child(2){color:var(--color-text-secondary);font-size:1.4em;opacity:.6}._logPlaceholderIcon_7a1x3_64{opacity:.3}._search_7a1x3_68{max-width:1000px} 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/Rules.e03c54a8.css: -------------------------------------------------------------------------------- 1 | ._RuleProviderItem_12aid_1{display:grid;grid-template-columns:40px 1fr 46px;height:100%}._left_12aid_7{display:inline-flex;align-items:center;color:var(--color-text-secondary);opacity:.4}._middle_12aid_14{display:grid;gap:6px;grid-template-rows:1fr auto auto;align-items:center}._gray_12aid_21{color:#777}._action_12aid_25{display:grid;gap:4px;grid-template-columns:auto 1fr;align-items:center}._refreshBtn_12aid_32{padding:5px}._rule_1ymqx_1{display:flex;align-items:center;padding:6px 15px}@media screen and (min-width: 30em){._rule_1ymqx_1{padding:10px 40px}}._left_1ymqx_12{width:40px;padding-right:15px;color:var(--color-text-secondary);opacity:.4}._a_1ymqx_19{display:flex;align-items:center;font-size:12px;opacity:.8}._b_1ymqx_26{padding:10px 0;font-family:Roboto Mono,Menlo,monospace;font-size:16px}@media screen and (min-width: 30em){._b_1ymqx_26{font-size:19px}}._type_1ymqx_37{width:110px}._header_1j1w3_1{display:grid;grid-template-columns:1fr minmax(auto,330px);align-items:center;padding-right:15px}@media screen and (min-width: 30em){._header_1j1w3_1{padding-right:40px}}._RuleProviderItemWrapper_1j1w3_17{padding:6px 15px}@media screen and (min-width: 30em){._RuleProviderItemWrapper_1j1w3_17{padding:10px 40px}} 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/Select.1e55eba1.css: -------------------------------------------------------------------------------- 1 | ._select_13zm8_1{height:40px;line-height:1.5;width:100%;padding-left:8px;-webkit-appearance:none;appearance:none;background-color:var(--color-input-bg);color:var(--color-text);padding-right:20px;border-radius:4px;border:1px solid var(--color-input-border);background-image:url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23999999%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23999999%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20);background-position:right 8px center;background-repeat:no-repeat}._select_13zm8_1:hover,._select_13zm8_1:focus{border-color:#343434;outline:none!important;color:var(--color-text-highlight);background-image:var(--select-bg-hover)}._select_13zm8_1:focus{box-shadow:#4299e199 0 0 0 3px}._select_13zm8_1 option{background-color:var(--color-background)} 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/Select.6c389032.js: -------------------------------------------------------------------------------- 1 | import{j as s}from"./index.171f553a.js";const o="_select_13zm8_1",r={select:o};function i({options:t,selected:c,onChange:l}){return s("select",{className:r.select,value:c,onChange:l,children:t.map(([e,n])=>s("option",{value:e,children:n},e))})}export{i as S}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/TextFitler.61537a57.js: -------------------------------------------------------------------------------- 1 | import{r as u,b as g,j as i,k as c,c as f,V as x,i as d}from"./index.171f553a.js";import{d as h}from"./debounce.c2d20996.js";function v(t,n){if(t==null)return{};var o=_(t,n),r,e;if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);for(e=0;e=0)&&(!Object.prototype.propertyIsEnumerable.call(t,r)||(o[r]=t[r]))}return o}function _(t,n){if(t==null)return{};var o={},r=Object.keys(t),e,s;for(s=0;s=0)&&(o[e]=t[e]);return o}var l=u.exports.forwardRef(function(t,n){var o=t.color,r=o===void 0?"currentColor":o,e=t.size,s=e===void 0?24:e,a=v(t,["color","size"]);return g("svg",{ref:n,xmlns:"http://www.w3.org/2000/svg",width:s,height:s,viewBox:"0 0 24 24",fill:"none",stroke:r,strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...a,children:[i("polyline",{points:"23 4 23 10 17 10"}),i("path",{d:"M20.49 15a9 9 0 1 1-2.12-9.36L23 10"})]})});l.propTypes={color:c.exports.string,size:c.exports.oneOfType([c.exports.string,c.exports.number])};l.displayName="RotateCw";const b=l,y="_rotate_1dspl_1",m="_isRotating_1dspl_5",R="_rotating_1dspl_1",p={rotate:y,isRotating:m,rotating:R};function P(t){const n=t.size||16,o=f(p.rotate,{[p.isRotating]:t.isRotating});return i("span",{className:o,children:i(b,{size:n})})}const{useCallback:w,useState:j,useMemo:k}=d;function O(t){const[,n]=x(t),[o,r]=j(""),e=k(()=>h(n,300),[n]);return[w(a=>{r(a.target.value),e(a.target.value)},[e]),o]}const T="_input_16a1f_1",C={input:T};function $(t){const[n,o]=O(t.textAtom);return i("input",{className:C.input,type:"text",value:o,onChange:n,placeholder:t.placeholder})}export{P as R,$ as T,b as a}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/TextFitler.b21c0577.css: -------------------------------------------------------------------------------- 1 | ._rotate_1dspl_1{display:inline-flex}._isRotating_1dspl_5{-webkit-animation:_rotating_1dspl_1 3s infinite linear;animation:_rotating_1dspl_1 3s infinite linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes _rotating_1dspl_1{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes _rotating_1dspl_1{0%{-webkit-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}._input_16a1f_1{-webkit-appearance:none;background-color:var(--color-input-bg);background-image:none;border-radius:20px;border:1px solid var(--color-input-border);box-sizing:border-box;color:#c1c1c1;display:inline-block;font-size:inherit;outline:none;padding:8px 15px;transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}._input_16a1f_1:focus{border:1px solid var(--color-focus-blue)} 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/chevron-down.dd238e96.js: -------------------------------------------------------------------------------- 1 | import{r as f,j as l,k as s}from"./index.171f553a.js";function c(r,i){if(r==null)return{};var n=v(r,i),o,e;if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(r);for(e=0;e=0)&&(!Object.prototype.propertyIsEnumerable.call(r,o)||(n[o]=r[o]))}return n}function v(r,i){if(r==null)return{};var n={},o=Object.keys(r),e,t;for(t=0;t=0)&&(n[e]=r[e]);return n}var p=f.exports.forwardRef(function(r,i){var n=r.color,o=n===void 0?"currentColor":n,e=r.size,t=e===void 0?24:e,a=c(r,["color","size"]);return l("svg",{ref:i,xmlns:"http://www.w3.org/2000/svg",width:t,height:t,viewBox:"0 0 24 24",fill:"none",stroke:o,strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...a,children:l("polyline",{points:"6 9 12 15 18 9"})})});p.propTypes={color:s.exports.string,size:s.exports.oneOfType([s.exports.string,s.exports.number])};p.displayName="ChevronDown";const u=p;export{u as C}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/debounce.c2d20996.js: -------------------------------------------------------------------------------- 1 | function O(e){var n=typeof e;return e!=null&&(n=="object"||n=="function")}var M=typeof global=="object"&&global&&global.Object===Object&&global;const R=M;var w=typeof self=="object"&&self&&self.Object===Object&&self,B=R||w||Function("return this")();const W=B;var F=function(){return W.Date.now()};const S=F;var G=/\s/;function U(e){for(var n=e.length;n--&&G.test(e.charAt(n)););return n}var _=/^\s+/;function D(e){return e&&e.slice(0,U(e)+1).replace(_,"")}var H=W.Symbol;const y=H;var L=Object.prototype,X=L.hasOwnProperty,q=L.toString,g=y?y.toStringTag:void 0;function z(e){var n=X.call(e,g),i=e[g];try{e[g]=void 0;var o=!0}catch{}var f=q.call(e);return o&&(n?e[g]=i:delete e[g]),f}var J=Object.prototype,K=J.toString;function Q(e){return K.call(e)}var V="[object Null]",Y="[object Undefined]",$=y?y.toStringTag:void 0;function Z(e){return e==null?e===void 0?Y:V:$&&$ in Object(e)?z(e):Q(e)}function ee(e){return e!=null&&typeof e=="object"}var ne="[object Symbol]";function te(e){return typeof e=="symbol"||ee(e)&&Z(e)==ne}var E=0/0,re=/^[-+]0x[0-9a-f]+$/i,ie=/^0b[01]+$/i,oe=/^0o[0-7]+$/i,ae=parseInt;function k(e){if(typeof e=="number")return e;if(te(e))return E;if(O(e)){var n=typeof e.valueOf=="function"?e.valueOf():e;e=O(n)?n+"":n}if(typeof e!="string")return e===0?e:+e;e=D(e);var i=ie.test(e);return i||oe.test(e)?ae(e.slice(2),i?2:8):re.test(e)?E:+e}var fe="Expected a function",ce=Math.max,ue=Math.min;function se(e,n,i){var o,f,s,u,r,c,d=0,v=!1,l=!1,T=!0;if(typeof e!="function")throw new TypeError(fe);n=k(n)||0,O(i)&&(v=!!i.leading,l="maxWait"in i,s=l?ce(k(i.maxWait)||0,n):s,T="trailing"in i?!!i.trailing:T);function j(t){var a=o,b=f;return o=f=void 0,d=t,u=e.apply(b,a),u}function N(t){return d=t,r=setTimeout(m,n),v?j(t):u}function P(t){var a=t-c,b=t-d,I=n-a;return l?ue(I,s-b):I}function h(t){var a=t-c,b=t-d;return c===void 0||a>=n||a<0||l&&b>=s}function m(){var t=S();if(h(t))return x(t);r=setTimeout(m,P(t))}function x(t){return r=void 0,T&&o?j(t):(o=f=void 0,u)}function A(){r!==void 0&&clearTimeout(r),d=0,o=c=f=r=void 0}function C(){return r===void 0?u:x(S())}function p(){var t=S(),a=h(t);if(o=arguments,f=this,c=t,a){if(r===void 0)return N(c);if(l)return clearTimeout(r),r=setTimeout(m,n),j(c)}return r===void 0&&(r=setTimeout(m,n)),u}return p.cancel=A,p.flush=C,p}export{se as d}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/en.fb34eaf7.js: -------------------------------------------------------------------------------- 1 | const e={Overview:"Overview",Proxies:"Proxies",Rules:"Rules",Conns:"Conns",Config:"Config",Logs:"Logs",Upload:"Upload",Download:"Download","Upload Total":"Upload Total","Download Total":"Download Total","Active Connections":"Active Connections","Pause Refresh":"Pause Refresh","Resume Refresh":"Resume Refresh",Up:"Up",Down:"Down","Test Latency":"Test Latency",settings:"settings",sort_in_grp:"Sorting in group",hide_unavail_proxies:"Hide unavailable proxies",auto_close_conns:"Automatically close old connections",order_natural:"Original order in config file",order_latency_asc:"By latency from small to big",order_latency_desc:"By latency from big to small",order_name_asc:"By name alphabetically (A-Z)",order_name_desc:"By name alphabetically (Z-A)",Connections:"Connections",Active:"Active",Closed:"Closed",switch_theme:"Switch theme",theme:"theme",about:"about",no_logs:"No logs yet, hang tight...",chart_style:"Chart Style",latency_test_url:"Latency Test URL",lang:"Language",update_all_rule_provider:"Update all rule providers",update_all_proxy_provider:"Update all proxy providers"};export{e as data}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/inter-latin-400-normal.0364d368.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/assets/inter-latin-400-normal.0364d368.woff2 -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/inter-latin-400-normal.3ea830d4.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/assets/inter-latin-400-normal.3ea830d4.woff -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/inter-latin-800-normal.a51ac27d.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/assets/inter-latin-800-normal.a51ac27d.woff2 -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/inter-latin-800-normal.d08d7178.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/assets/inter-latin-800-normal.d08d7178.woff -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/logs.43986220.js: -------------------------------------------------------------------------------- 1 | import{E as w,G as D,H as u}from"./index.171f553a.js";const v="/logs",L=new TextDecoder("utf-8"),M=()=>Math.floor((1+Math.random())*65536).toString(16);let h=!1,i=!1,f="",s,g;function m(e,n){let t;try{t=JSON.parse(e)}catch{console.log("JSON.parse error",JSON.parse(e))}const r=new Date,l=$(r);t.time=l,t.id=+r-0+M(),t.even=h=!h,n(t)}function $(e){const n=e.getFullYear()%100,t=u(e.getMonth()+1,2),r=u(e.getDate(),2),l=u(e.getHours(),2),o=u(e.getMinutes(),2),c=u(e.getSeconds(),2);return`${n}-${t}-${r} ${l}:${o}:${c}`}function p(e,n){return e.read().then(({done:t,value:r})=>{const l=L.decode(r,{stream:!t});f+=l;const o=f.split(` 2 | `),c=o[o.length-1];for(let d=0;de[t]).join("|")}let b,a;function k(e,n){if(e.logLevel==="uninit"||i||s&&s.readyState===1)return;g=n;const t=w(e,v);s=new WebSocket(t),s.addEventListener("error",()=>{y(e,n)}),s.addEventListener("message",function(r){m(r.data,n)})}function H(){s.close(),a&&a.abort()}function O(e){!g||!s||(s.close(),i=!1,k(e,g))}function y(e,n){if(a&&S(e)!==b)a.abort();else if(i)return;i=!0,b=S(e),a=new AbortController;const t=a.signal,{url:r,init:l}=D(e);fetch(r+v+"?level="+e.logLevel,{...l,signal:t}).then(o=>{const c=o.body.getReader();p(c,n)},o=>{i=!1,!t.aborted&&console.log("GET /logs error:",o.message)})}export{k as f,O as r,H as s}; 3 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/play.7b1a5f99.js: -------------------------------------------------------------------------------- 1 | import{r as c,b as u,j as p,k as s}from"./index.171f553a.js";function y(e,n){if(e==null)return{};var i=g(e,n),t,r;if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&(!Object.prototype.propertyIsEnumerable.call(e,t)||(i[t]=e[t]))}return i}function g(e,n){if(e==null)return{};var i={},t=Object.keys(e),r,o;for(o=0;o=0)&&(i[r]=e[r]);return i}var l=c.exports.forwardRef(function(e,n){var i=e.color,t=i===void 0?"currentColor":i,r=e.size,o=r===void 0?24:r,a=y(e,["color","size"]);return u("svg",{ref:n,xmlns:"http://www.w3.org/2000/svg",width:o,height:o,viewBox:"0 0 24 24",fill:"none",stroke:t,strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...a,children:[p("rect",{x:"6",y:"4",width:"4",height:"16"}),p("rect",{x:"14",y:"4",width:"4",height:"16"})]})});l.propTypes={color:s.exports.string,size:s.exports.oneOfType([s.exports.string,s.exports.number])};l.displayName="Pause";const d=l;function h(e,n){if(e==null)return{};var i=v(e,n),t,r;if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&(!Object.prototype.propertyIsEnumerable.call(e,t)||(i[t]=e[t]))}return i}function v(e,n){if(e==null)return{};var i={},t=Object.keys(e),r,o;for(o=0;o=0)&&(i[r]=e[r]);return i}var f=c.exports.forwardRef(function(e,n){var i=e.color,t=i===void 0?"currentColor":i,r=e.size,o=r===void 0?24:r,a=h(e,["color","size"]);return p("svg",{ref:n,xmlns:"http://www.w3.org/2000/svg",width:o,height:o,viewBox:"0 0 24 24",fill:"none",stroke:t,strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...a,children:p("polygon",{points:"5 3 19 12 5 21 5 3"})})});f.propTypes={color:s.exports.string,size:s.exports.oneOfType([s.exports.string,s.exports.number])};f.displayName="Play";const w=f;export{w as P,d as a}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/roboto-mono-latin-400-normal.7295944e.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/assets/roboto-mono-latin-400-normal.7295944e.woff2 -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/roboto-mono-latin-400-normal.dffdffa7.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/assets/roboto-mono-latin-400-normal.dffdffa7.woff -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/useRemainingViewPortHeight.7395542b.js: -------------------------------------------------------------------------------- 1 | import{i as r}from"./index.171f553a.js";const{useState:s,useRef:u,useCallback:a,useLayoutEffect:c}=r;function d(){const t=u(null),[n,i]=s(200),e=a(()=>{const{top:o}=t.current.getBoundingClientRect();i(window.innerHeight-o)},[]);return c(()=>(e(),window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e)}),[e]),[t,n]}export{d as u}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/assets/zh.9b79b7bf.js: -------------------------------------------------------------------------------- 1 | const u={Overview:"\u6982\u89C8",Proxies:"\u4EE3\u7406",Rules:"\u89C4\u5219",Conns:"\u8FDE\u63A5",Config:"\u914D\u7F6E",Logs:"\u65E5\u5FD7",Upload:"\u4E0A\u4F20",Download:"\u4E0B\u8F7D","Upload Total":"\u4E0A\u4F20\u603B\u91CF","Download Total":"\u4E0B\u8F7D\u603B\u91CF","Active Connections":"\u6D3B\u52A8\u8FDE\u63A5","Pause Refresh":"\u6682\u505C\u5237\u65B0","Resume Refresh":"\u7EE7\u7EED\u5237\u65B0",Up:"\u4E0A\u4F20",Down:"\u4E0B\u8F7D","Test Latency":"\u5EF6\u8FDF\u6D4B\u901F",settings:"\u8BBE\u7F6E",sort_in_grp:"\u4EE3\u7406\u7EC4\u6761\u76EE\u6392\u5E8F",hide_unavail_proxies:"\u9690\u85CF\u4E0D\u53EF\u7528\u4EE3\u7406",auto_close_conns:"\u5207\u6362\u4EE3\u7406\u65F6\u81EA\u52A8\u65AD\u5F00\u65E7\u8FDE\u63A5",order_natural:"\u539F config \u6587\u4EF6\u4E2D\u7684\u6392\u5E8F",order_latency_asc:"\u6309\u5EF6\u8FDF\u4ECE\u5C0F\u5230\u5927",order_latency_desc:"\u6309\u5EF6\u8FDF\u4ECE\u5927\u5230\u5C0F",order_name_asc:"\u6309\u540D\u79F0\u5B57\u6BCD\u6392\u5E8F (A-Z)",order_name_desc:"\u6309\u540D\u79F0\u5B57\u6BCD\u6392\u5E8F (Z-A)",Connections:"\u8FDE\u63A5",Active:"\u6D3B\u52A8",Closed:"\u5DF2\u65AD\u5F00",switch_theme:"\u5207\u6362\u4E3B\u9898",theme:"\u4E3B\u9898",about:"\u5173\u4E8E",no_logs:"\u6682\u65E0\u65E5\u5FD7...",chart_style:"\u6D41\u91CF\u56FE\u6837\u5F0F",latency_test_url:"\u5EF6\u8FDF\u6D4B\u901F URL",lang:"\u8BED\u8A00",update_all_rule_provider:"\u66F4\u65B0\u6240\u6709 rule provider",update_all_proxy_provider:"\u66F4\u65B0\u6240\u6709 proxy providers"};export{u as data}; 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | yacd 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"yacd","short_name":"yacd","start_url":"./","display":"standalone","background_color":"#ffffff","lang":"en","scope":"./"} 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/public/registerSW.js: -------------------------------------------------------------------------------- 1 | if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('./sw.js', { scope: './' })})} -------------------------------------------------------------------------------- /clash/tests/data/config/public/yacd-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/yacd-128.png -------------------------------------------------------------------------------- /clash/tests/data/config/public/yacd-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/yacd-64.png -------------------------------------------------------------------------------- /clash/tests/data/config/public/yacd.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/public/yacd.ico -------------------------------------------------------------------------------- /clash/tests/data/config/rule-set-classical.yaml: -------------------------------------------------------------------------------- 1 | payload: 2 | - DOMAIN-REGEX,^www.twitter.com$ 3 | -------------------------------------------------------------------------------- /clash/tests/data/config/rule-set.yaml: -------------------------------------------------------------------------------- 1 | payload: 2 | - 'httpbin.yba.dev' -------------------------------------------------------------------------------- /clash/tests/data/config/shadowquic.yaml: -------------------------------------------------------------------------------- 1 | inbound: 2 | type: shadowquic 3 | bind-addr: 0.0.0.0:10002 4 | jls-pwd: "12345678" 5 | jls-iv: "87654321" 6 | jls-upstream: "echo.free.beeceptor.com:443" # domain + port, domain must be the same as client 7 | alpn: ["h3"] 8 | congestion-control: bbr 9 | zero-rtt: true 10 | outbound: 11 | type: direct 12 | log-level: "trace" -------------------------------------------------------------------------------- /clash/tests/data/config/simple.yaml: -------------------------------------------------------------------------------- 1 | mixed-port: 8899 2 | external-controller: 127.0.0.1:9090 3 | mode: global 4 | bind-address: "0.0.0.0" 5 | -------------------------------------------------------------------------------- /clash/tests/data/config/socks5.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8888 3 | socks-port: 8889 4 | mixed-port: 8899 5 | 6 | mode: rule 7 | log-level: debug 8 | external-controller: 127.0.0.1:6170 9 | 10 | 11 | proxies: 12 | - name: "socks5-noauth" 13 | type: socks5 14 | server: 10.0.0.13 15 | port: 10800 16 | udp: true 17 | 18 | - name: "socks5-auth" 19 | type: socks5 20 | server: 10.0.0.13 21 | port: 10801 22 | username: user 23 | password: password 24 | udp: true 25 | 26 | - name: "socks5-tls" 27 | type: socks5 28 | server: 10.0.0.13 29 | port: 10802 30 | username: user 31 | password: password 32 | tls: true 33 | udp: true 34 | skip-cert-verify: true 35 | rules: 36 | # - MATCH, socks5-noauth 37 | # - MATCH, socks5-auth 38 | - MATCH, socks5-tls 39 | ... 40 | -------------------------------------------------------------------------------- /clash/tests/data/config/ss.json: -------------------------------------------------------------------------------- 1 | { 2 | "server":"0.0.0.0", 3 | "server_port": 10004, 4 | "password":"FzcLbKs2dY9mhL", 5 | "timeout":300, 6 | "method":"aes-256-gcm", 7 | "nameserver":"1.1.1.1", 8 | "mode":"tcp_and_udp", 9 | "plugin":"v2ray-plugin", 10 | "plugin_opts":"server;tls;host=example.org" 11 | } -------------------------------------------------------------------------------- /clash/tests/data/config/ss.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8888 3 | socks-port: 8889 4 | mixed-port: 8899 5 | 6 | dns: 7 | enable: true 8 | listen: 127.0.0.1:53533 9 | # ipv6: false # when the false, response to AAAA questions will be empty 10 | 11 | # These nameservers are used to resolve the DNS nameserver hostnames below. 12 | # Specify IP addresses only 13 | default-nameserver: 14 | - 114.114.114.114 15 | - 8.8.8.8 16 | enhanced-mode: fake-ip # or fake-ip 17 | fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR 18 | # use-hosts: true # lookup hosts and return IP record 19 | 20 | # Hostnames in this list will not be resolved with fake IPs 21 | # i.e. questions to these domain names will always be answered with their 22 | # real IP addresses 23 | # fake-ip-filter: 24 | # - '*.lan' 25 | # - localhost.ptlogin2.qq.com 26 | 27 | # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. 28 | # All DNS questions are sent directly to the nameserver, without proxies 29 | # involved. Clash answers the DNS question with the first result gathered. 30 | nameserver: 31 | - 114.114.114.114 # default value 32 | - 8.8.8.8 # default value 33 | - tls://dns.google:853 # DNS over TLS 34 | - https://1.1.1.1/dns-query # DNS over HTTPS 35 | 36 | allow-lan: true 37 | mode: rule 38 | log-level: debug 39 | external-controller: 127.0.0.1:6170 40 | experimental: 41 | ignore-resolve-fail: true 42 | 43 | proxies: 44 | - name: "ss-01" 45 | type: ss 46 | server: 10.0.0.13 47 | port: 8388 48 | cipher: aes-256-gcm 49 | password: "password" 50 | udp: true 51 | connect-via: ss-02 52 | # dialer-proxy: ss-02 53 | 54 | - name: "ss-02" 55 | type: ss 56 | server: 10.0.0.13 57 | port: 8388 58 | cipher: aes-256-gcm 59 | password: "password" 60 | udp: true 61 | 62 | - name: ss-2022 63 | type: ss 64 | server: 127.0.0.1 65 | port: 8390 66 | cipher: 2022-blake3-aes-256-gcm 67 | password: 3SYJ/f8nmVuzKvKglykRQDSgg10e/ADilkdRWrrY9HU=:4w0GKJ9U3Ox7CIXGU4A3LDQAqP6qrp/tUi/ilpOR9p4= 68 | 69 | proxy-groups: 70 | - name: "udp-relay" 71 | type: relay 72 | proxies: 73 | - ss-01 74 | - ss-02 75 | - ss-2022 76 | rules: 77 | - MATCH, ss-2022 78 | -------------------------------------------------------------------------------- /clash/tests/data/config/ssh/.ssh/authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCgWVElNdRL4sx7013uQ8FmuWxIrDdYzjuKKc45ef1uEHkORlj3QZ8RAemAF8hiHa9uTL/i79pAdOHPST8nLgPvP37kTIrmxy2wD1D3Gi43UNSBTFlFrcyFORieB0EvgCaRqPueQ4Rj5gOjx9yJ+ald/U41D/I/3bRXNw0BPeUWildnh6zJbcRuYXXq4srFKAlTA9xT0Z/J5c0BVuSd2v5LaZgbnj4+VJtx7VBv4NntL0NCCyMjLt61lltgV0LBm9aY4SPe2xpEn5HBKkNYFr/1aRRnbs9k0Cfe7eC7BJKKEq4ZpYk0d2kGuwXqBrd1Xem12wkvwkOL5SOiQ3mmjWDUAUDyv4YiW65kx83twUMJHmov//ouBrhtMR/aAVBl/6yPzopLThUK1JEMxOBdNE2R4NbnARWL5PUAQTtPKfKq//sSaPiG62+6Vj+Mph+qD2cu1yLoFBw4pk/t5lwXV7IQ20mP0UymOYJ6XxMfquEmkTVN+K8ovfiQtIHiZBvu1P0= vendettareborn@proton.me 2 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMkoG/VIYONdo3B0TRWLuniNViuETe7GnPa4PWac8hTv vendettareborn@proton.me 3 | -------------------------------------------------------------------------------- /clash/tests/data/config/ssh/.ssh/test_ed25519: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACDJKBv1SGDjXaNwdE0Vi7p4jVYrhE3uxpz2uD1mnPIU7wAAAKBknE6IZJxO 4 | iAAAAAtzc2gtZWQyNTUxOQAAACDJKBv1SGDjXaNwdE0Vi7p4jVYrhE3uxpz2uD1mnPIU7w 5 | AAAED2upkxism+P9SqtaYMqLTyvH0V7KnGL+YpdHCAwsydcskoG/VIYONdo3B0TRWLuniN 6 | ViuETe7GnPa4PWac8hTvAAAAGHZlbmRldHRhcmVib3JuQHByb3Rvbi5tZQECAwQF 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /clash/tests/data/config/tor.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8888 3 | socks-port: 8889 4 | mixed-port: 8899 5 | 6 | mode: rule 7 | log-level: debug 8 | external-controller: 127.0.0.1:6170 9 | 10 | 11 | proxies: 12 | - name: "tor" 13 | type: tor 14 | - name: "ss-02" 15 | type: ss 16 | server: 10.0.0.13 17 | port: 8388 18 | cipher: aes-256-gcm 19 | password: "password" 20 | udp: true 21 | 22 | rules: 23 | - MATCH, tor 24 | ... 25 | -------------------------------------------------------------------------------- /clash/tests/data/config/tproxy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | tproxy-port: 8900 3 | 4 | mode: rule 5 | log-level: debug 6 | 7 | rules: 8 | - MATCH, DIRECT 9 | ... 10 | -------------------------------------------------------------------------------- /clash/tests/data/config/trojan-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "trojan", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "password": "example", 11 | "email": "grpc@example.com" 12 | } 13 | ] 14 | }, 15 | "streamSettings": { 16 | "network": "grpc", 17 | "security": "tls", 18 | "tlsSettings": { 19 | "certificates": [ 20 | { 21 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 22 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 23 | } 24 | ] 25 | }, 26 | "grpcSettings": { 27 | "serviceName": "example" 28 | } 29 | } 30 | } 31 | ], 32 | "outbounds": [ 33 | { 34 | "protocol": "freedom" 35 | } 36 | ], 37 | "log": { 38 | "loglevel": "debug" 39 | } 40 | } -------------------------------------------------------------------------------- /clash/tests/data/config/trojan-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "disable_http_check": true, 6 | "password": [ 7 | "example" 8 | ], 9 | "websocket": { 10 | "enabled": true, 11 | "path": "/", 12 | "host": "example.org" 13 | }, 14 | "ssl": { 15 | "verify": true, 16 | "cert": "/fullchain.pem", 17 | "key": "/privkey.pem", 18 | "sni": "example.org" 19 | } 20 | } -------------------------------------------------------------------------------- /clash/tests/data/config/tuic.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "0.0.0.0:10002", 3 | "users": { 4 | "00000000-0000-0000-0000-000000000001": "passwd" 5 | }, 6 | "certificate": "/opt/tuic/fullchain.pem", 7 | "private_key": "/opt/tuic/privkey.pem", 8 | "ip": "0.0.0.0", 9 | "dual_stack": false, 10 | "congestion_controller": "bbr", 11 | "alpn": [ 12 | "h3" 13 | ] 14 | } -------------------------------------------------------------------------------- /clash/tests/data/config/tun.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8888 3 | 4 | tun: 5 | enable: true 6 | device-id: "dev://utun1989" 7 | route-all: false 8 | gateway: "198.19.0.1/24" 9 | so-mark: 3389 10 | dns-hijack: false 11 | # dns-hijack: 12 | # - 1.1.1.1:53 13 | # routes: 14 | # - 1.1.1.1/32 15 | 16 | rules: 17 | - MATCH, DIRECT 18 | -------------------------------------------------------------------------------- /clash/tests/data/config/uot.yaml: -------------------------------------------------------------------------------- 1 | proxies: 2 | - name: plain-vmess 3 | type: vmess 4 | server: 10.0.0.13 5 | port: 16823 6 | uuid: b831381d-6324-4d53-ad4f-8cda48b30811 7 | alterId: 0 8 | cipher: auto 9 | udp: true 10 | skip-cert-verify: true 11 | 12 | - name: ws-vmess 13 | type: vmess 14 | server: 10.0.0.13 15 | port: 16824 16 | uuid: b831381d-6324-4d53-ad4f-8cda48b30811 17 | alterId: 0 18 | cipher: auto 19 | udp: true 20 | skip-cert-verify: true 21 | network: ws 22 | ws-opts: 23 | path: /api/v3/download.getFile 24 | headers: 25 | Host: www.amazon.com 26 | 27 | - name: "trojan" 28 | type: trojan 29 | server: 10.0.0.13 30 | port: 9443 31 | password: password1 32 | udp: true 33 | # sni: example.com # aka server name 34 | alpn: 35 | - h2 36 | - http/1.1 37 | skip-cert-verify: true 38 | -------------------------------------------------------------------------------- /clash/tests/data/config/vmess-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "grpc", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "grpcSettings": { 26 | "serviceName": "example!" 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ], 36 | "log": { 37 | "loglevel": "debug" 38 | } 39 | } -------------------------------------------------------------------------------- /clash/tests/data/config/vmess-http2.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "http", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "httpSettings": { 26 | "host": [ 27 | "example.org" 28 | ], 29 | "path": "/test" 30 | } 31 | } 32 | } 33 | ], 34 | "outbounds": [ 35 | { 36 | "protocol": "freedom" 37 | } 38 | ], 39 | "log": { 40 | "loglevel": "debug" 41 | } 42 | } -------------------------------------------------------------------------------- /clash/tests/data/config/vmess-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /clash/tests/data/config/wg.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | port: 8888 3 | socks-port: 8889 4 | mixed-port: 8899 5 | 6 | 7 | dns: 8 | enable: true 9 | listen: 127.0.0.1:53533 10 | # ipv6: false # when the false, response to AAAA questions will be empty 11 | 12 | # These nameservers are used to resolve the DNS nameserver hostnames below. 13 | # Specify IP addresses only 14 | default-nameserver: 15 | - 114.114.114.114 16 | - 8.8.8.8 17 | enhanced-mode: fake-ip # or fake-ip 18 | fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR 19 | # use-hosts: true # lookup hosts and return IP record 20 | 21 | # Hostnames in this list will not be resolved with fake IPs 22 | # i.e. questions to these domain names will always be answered with their 23 | # real IP addresses 24 | # fake-ip-filter: 25 | # - '*.lan' 26 | # - localhost.ptlogin2.qq.com 27 | 28 | # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. 29 | # All DNS questions are sent directly to the nameserver, without proxies 30 | # involved. Clash answers the DNS question with the first result gathered. 31 | nameserver: 32 | - 114.114.114.114 # default value 33 | - 8.8.8.8 # default value 34 | - tls://dns.google:853 # DNS over TLS 35 | - https://1.1.1.1/dns-query # DNS over HTTPS 36 | 37 | allow-lan: true 38 | mode: rule 39 | log-level: debug 40 | external-controller: 127.0.0.1:6170 41 | experimental: 42 | ignore-resolve-fail: true 43 | 44 | proxies: 45 | - name: "wg" 46 | type: wireguard 47 | server: engage.cloudflareclient.com 48 | port: 2408 49 | private-key: 0LQye/+HjLvgnXLs5ETQcHe5AcR7G4Bv78xu6Qja230= 50 | ip: 172.16.0.2/32 51 | ipv6: 2606:4700:110:82f3:873f:ed26:e24d:c2cb/128 52 | public-key: bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo= 53 | allowed-ips: ['0.0.0.0/0', '::/0'] 54 | remote-dns-resolve: true 55 | dns: 56 | - 1.1.1.1 57 | udp: true 58 | connect-via: plain-vmess 59 | - name: plain-vmess 60 | type: vmess 61 | server: 10.0.0.13 62 | port: 16823 63 | uuid: b831381d-6324-4d53-ad4f-8cda48b30811 64 | alterId: 0 65 | cipher: auto 66 | udp: true 67 | skip-cert-verify: true 68 | 69 | rules: 70 | - MATCH, wg 71 | ... 72 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/.donoteditthisfile: -------------------------------------------------------------------------------- 1 | ORIG_SERVERURL="127.0.0.1" 2 | ORIG_SERVERPORT="10002" 3 | ORIG_PEERDNS="10.13.13.1" 4 | ORIG_PEERS="1" 5 | ORIG_INTERFACE="10.13.13" 6 | ORIG_ALLOWEDIPS="0.0.0.0/0" 7 | ORIG_PERSISTENTKEEPALIVE_PEERS="" 8 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/coredns/Corefile: -------------------------------------------------------------------------------- 1 | . { 2 | loop 3 | health 4 | forward . /etc/resolv.conf 5 | } 6 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/peer1/peer1.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = 10.13.13.2 3 | PrivateKey = KIlDUePHyYwzjgn18przw/ZwPioJhh2aEyhxb/dtCXI= 4 | ListenPort = 51820 5 | DNS = 10.13.13.1 6 | 7 | [Peer] 8 | PublicKey = INBZyvB715sA5zatkiX8Jn3Dh5tZZboZ09x4pkr66ig= 9 | PresharedKey = +JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc= 10 | Endpoint = 127.0.0.1:10002 11 | AllowedIPs = 0.0.0.0/0 12 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/peer1/peer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash/tests/data/config/wg_config/peer1/peer1.png -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/peer1/presharedkey-peer1: -------------------------------------------------------------------------------- 1 | +JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc= 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/peer1/privatekey-peer1: -------------------------------------------------------------------------------- 1 | KIlDUePHyYwzjgn18przw/ZwPioJhh2aEyhxb/dtCXI= 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/peer1/publickey-peer1: -------------------------------------------------------------------------------- 1 | H7NHC22d44AhrJf7BSzbNJrW1wiTDCRYNfP0rQicM3g= 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/server/privatekey-server: -------------------------------------------------------------------------------- 1 | CA7cMGAh7BF/kD000ZRN+ZXDe1SGd1Z3kqNjQxnCAmQ= 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/server/publickey-server: -------------------------------------------------------------------------------- 1 | INBZyvB715sA5zatkiX8Jn3Dh5tZZboZ09x4pkr66ig= 2 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/templates/peer.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = ${CLIENT_IP} 3 | PrivateKey = $(cat /config/${PEER_ID}/privatekey-${PEER_ID}) 4 | ListenPort = 51820 5 | DNS = ${PEERDNS} 6 | 7 | [Peer] 8 | PublicKey = $(cat /config/server/publickey-server) 9 | PresharedKey = $(cat /config/${PEER_ID}/presharedkey-${PEER_ID}) 10 | Endpoint = ${SERVERURL}:${SERVERPORT} 11 | AllowedIPs = ${ALLOWEDIPS} 12 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/templates/server.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = ${INTERFACE}.1 3 | ListenPort = 10002 4 | PrivateKey = $(cat /config/server/privatekey-server) 5 | PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE 6 | PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE 7 | -------------------------------------------------------------------------------- /clash/tests/data/config/wg_config/wg_confs/wg0.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = 10.13.13.1 3 | ListenPort = 10002 4 | PrivateKey = CA7cMGAh7BF/kD000ZRN+ZXDe1SGd1Z3kqNjQxnCAmQ= 5 | PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE 6 | PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE 7 | 8 | [Peer] 9 | # peer1 10 | PublicKey = H7NHC22d44AhrJf7BSzbNJrW1wiTDCRYNfP0rQicM3g= 11 | PresharedKey = +JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc= 12 | AllowedIPs = 10.13.13.2/32 13 | 14 | -------------------------------------------------------------------------------- /clash/tests/data/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | socks5-auth: 5 | image: ghcr.io/wzshiming/socks5/socks5:v0.4.3 6 | network_mode: "host" 7 | command: 8 | - "-u" 9 | - "user" 10 | - "-p" 11 | - "password" 12 | - "-a" 13 | - "0.0.0.0:10801" 14 | restart: unless-stopped 15 | 16 | socks5-noauth: 17 | image: ghcr.io/wzshiming/socks5/socks5:v0.4.3 18 | network_mode: "host" 19 | command: 20 | - "-a" 21 | - "0.0.0.0:10800" 22 | restart: unless-stopped 23 | shadowsocks: 24 | build: ./ss 25 | network_mode: "host" 26 | command: ["-s", "ss://AEAD_AES_256_GCM:password@:8388", "-udp", "-verbose"] 27 | restart: unless-stopped 28 | 29 | v2ray-vmess: 30 | image: v2fly/v2fly-core 31 | environment: 32 | - V2RAY_VMESS_AEAD_FORCED=false 33 | network_mode: "host" 34 | command: ["run", "-c", "/etc/v2ray/config.json"] 35 | volumes: 36 | - type: bind 37 | source: ./v2ray/config.json 38 | target: /etc/v2ray/config.json 39 | - type: bind 40 | source: ./v2ray/cert.pem 41 | target: /etc/v2ray/v2ray.crt 42 | - type: bind 43 | source: ./v2ray/key.pem 44 | target: /etc/v2ray/v2ray.key 45 | restart: unless-stopped 46 | 47 | nginx: 48 | image: nginx 49 | network_mode: "host" 50 | volumes: 51 | - type: bind 52 | source: ./nginx/nginx.conf 53 | target: /etc/nginx/nginx.conf 54 | - type: bind 55 | source: ./v2ray/cert.pem 56 | target: /etc/v2ray/v2ray.crt 57 | - type: bind 58 | source: ./v2ray/key.pem 59 | target: /etc/v2ray/v2ray.key 60 | restart: unless-stopped 61 | 62 | hysteria2: 63 | image: tobyxdd/hysteria 64 | network_mode: "host" 65 | command: 66 | - server 67 | - "-c" 68 | - "/etc/hysteria/config.yaml" 69 | volumes: 70 | - type: bind 71 | source: ./hysteria2/config.yaml 72 | target: /etc/hysteria/config.yaml 73 | - type: bind 74 | source: ./v2ray/cert.pem 75 | target: /etc/hysteria/cert.pem 76 | - type: bind 77 | source: ./v2ray/key.pem 78 | target: /etc/hysteria/key.pem 79 | restart: unless-stopped -------------------------------------------------------------------------------- /clash/tests/data/docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 4096; ## Default: 1024 3 | } 4 | 5 | http { 6 | error_log /tmp/error.log debug; 7 | 8 | server { 9 | listen 19443 ssl; 10 | server_name localhost; 11 | http2 on; 12 | 13 | ssl_certificate /etc/v2ray/v2ray.crt; 14 | ssl_certificate_key /etc/v2ray/v2ray.key; 15 | ssl_protocols TLSv1.2 TLSv1.3; 16 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 17 | 18 | 19 | location /abc/Tun { 20 | grpc_pass grpc://127.0.0.1:16825; 21 | } 22 | 23 | location /def/Tun { 24 | grpc_pass grpc://127.0.0.1:9444; 25 | } 26 | } 27 | } 28 | 29 | stream { 30 | 31 | server { 32 | listen 10802 ssl; 33 | 34 | ssl_certificate /etc/v2ray/v2ray.crt; 35 | ssl_certificate_key /etc/v2ray/v2ray.key; 36 | ssl_protocols TLSv1.2 TLSv1.3; 37 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 38 | 39 | proxy_pass 127.0.0.1:10801; 40 | } 41 | } -------------------------------------------------------------------------------- /clash/tests/data/docker/ss/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 2 | 3 | RUN go install github.com/shadowsocks/go-shadowsocks2@latest 4 | 5 | ENTRYPOINT ["go-shadowsocks2"] -------------------------------------------------------------------------------- /clash/tests/data/docker/ss/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | // AEAD-2022 5 | "server": "::", 6 | "mode": "tcp_and_udp", 7 | "server_port": 8390, 8 | "method": "2022-blake3-aes-256-gcm", 9 | "password": "3SYJ/f8nmVuzKvKglykRQDSgg10e/ADilkdRWrrY9HU=", 10 | // For Server (OPTIONAL) 11 | // Support multiple users with Extensible Identity Header 12 | // https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2022-2-shadowsocks-2022-extensible-identity-headers.md 13 | "users": [ 14 | { 15 | "name": "username", 16 | // User's password must have the same length as server's password 17 | "password": "4w0GKJ9U3Ox7CIXGU4A3LDQAqP6qrp/tUi/ilpOR9p4=" 18 | } 19 | ], 20 | } 21 | ], 22 | } -------------------------------------------------------------------------------- /clash/tests/data/docker/v2ray/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLzCCAhegAwIBAgIQAc1RNqU2laok+DH4GTtUGDANBgkqhkiG9w0BAQsFADAS 3 | MRAwDgYDVQQKEwdBY21lIENvMB4XDTIzMDgxODEwMjU1MloXDTI0MDgxNzEwMjU1 4 | MlowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBAKwY24NJm0blObfiZP99h63u7Oy+jvOBqZ15DU4nF0G8xJ6bcmIsBREN 6 | XQ1kVL861MH7wTPwRG62GWZTuFGrBeag/D1LW8Zx+aHMK5ndHI/Mks6sZ0YwMdK1 7 | KrmJlqiLtb947dQoKEBGk12qA6p8U+Ywbmv6nTVs/r7zG4srqGJSnf5qgmdUAP4K 8 | +tRTsJZ5IP5rJ6+tK7Qp9HwKM9L/I+JAz4Ehwujt9nKtKTHLHv/ObcK4eUEtBfKV 9 | VbxsFeX0G/Su0fDX1M/TVGVMCzFD/rSt1tjzOaZW30HqRD6ZpGFvpOtfZeCiUyRM 10 | Yp8m5mrs2B+LjqMabLfuD2hLJrlATzUCAwEAAaOBgDB+MA4GA1UdDwEB/wQEAwIC 11 | pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW 12 | BBTCLqiC1GarCl9QBJBN7Ghwl1dzRzAnBgNVHREEIDAeghxsb2NhbC12MnJheS10 13 | ZXN0LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBiOkEsKAqghSmgGqKq 14 | E648J2JFm3RU8RQ6h4O+oYiuD2y++v5XfEwvaUVy9v9OkZZNMT2nk2Z5E3ecRL4d 15 | Ajd8sFutLJa4L4xz19OPbhwpQqNTqwgUGsRW4y0OJI0baY1hx8YmGW2vBJTB78bA 16 | e9P12HNJXz0D8iumCwSir2oxTamA89q8vRiHxlchhT11BJPf8o+OEuXhzJezku0H 17 | eMLUwPLGIz1XA89ZHExshcgyshQo3Kx25iRRmvhW4sc7ho/XriF7C3CHvSPo1/23 18 | nS0bv6pFFeOZrOPmsP+x96H9i5njNax5NX/YbYhh6bqJ8cAMh7tVBVcKhSximIFB 19 | 2fG/ 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /clash/tests/data/docker/v2ray/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsGNuDSZtG5Tm3 3 | 4mT/fYet7uzsvo7zgamdeQ1OJxdBvMSem3JiLAURDV0NZFS/OtTB+8Ez8ERuthlm 4 | U7hRqwXmoPw9S1vGcfmhzCuZ3RyPzJLOrGdGMDHStSq5iZaoi7W/eO3UKChARpNd 5 | qgOqfFPmMG5r+p01bP6+8xuLK6hiUp3+aoJnVAD+CvrUU7CWeSD+ayevrSu0KfR8 6 | CjPS/yPiQM+BIcLo7fZyrSkxyx7/zm3CuHlBLQXylVW8bBXl9Bv0rtHw19TP01Rl 7 | TAsxQ/60rdbY8zmmVt9B6kQ+maRhb6TrX2XgolMkTGKfJuZq7Ngfi46jGmy37g9o 8 | Sya5QE81AgMBAAECggEAakvEMdgh520n9FMKbN/9EMp1Xljo9LCOsiwVssLkU38j 9 | to9u3AIycvDdG6tvyNmulc5I7CqoKfWhxJlLTG4k6+ldQwKrweud83inKZbv0EXc 10 | G3lTJIAaFfo+VHEONDZu4L/xrcvL6L9uwDiFoSS/sXsSiPE3bstOoWSJC7HAhFFd 11 | eJbZkZQ9cNX35XuRixyPHDELwoczGjuyljEWZoQZTXJm0doH+lEd3uRlLWzV7Euw 12 | Eb8elTZnV7OhUWfeqlrnHDad5fNKaTyu9ei/zcgTYq6YlJ+4fSnJUUZKPddoaw9P 13 | l9tDs8Y44uaA4QUY+Ij0xu4HTWsSrWi7gThV35aGlQKBgQDecKwS0dgpcCd81/S9 14 | u+uaOs3u9js0giPxma8m68AxcaspnQvXct8CBgfU3u1QI1IzkzEYWEfqUZeMRkVs 15 | IHhvM28UertupDHLfTs0Te0ysW/Oy3QNfhV1EoG4fYarhaqGhxIPu+7Rt8v4WEJW 16 | e+N12E4jQ8mx3cIh8p9xeS/oqwKBgQDGD8gMOhsK62JO2sLDrtuauz1lhzfV1Qhg 17 | r7nD5GP20A68sDcFpdMzsTsWbEO9icxFbwxuxtn8gak+eQ8a1dJIYBjqKf2pL3ND 18 | 5HGderRu1rnVabWA6NPk+G8bdA81IDNFSpNswJuFDzw1lg3HDVLzvlO8fanDJyeA 19 | SltZ7aZnnwKBgQCd2n+Ca4BnB7w+EFMSQkWUEZ8KsZqaTLCNR27QxASEEhJRWa2J 20 | m28+1GDCY+EtOaOgDhiNGjkOxBAz77pcXT6aS3nMorxYbBUaPyjAmXx2uQyLSD53 21 | RL6dciC0eAAVwKmfBkN+/vMfyLrq5ldNYGWuv23UAMsleiXGSZN0x3eEOQKBgB5z 22 | drJKjLXVErxE7gTf8WuMthfR/kemBS+4VLtFdgkQW/OutAbuQ9aCvS7pXlDZysoy 23 | FJtDf2hPFxI/0o6xqS7vd2UpJ5LHdNVPXhh0MSGJafDh28ICCfH+MDbsVRo55SgW 24 | GyxxQHfoq70hYOTlq4dGD+G/AEa80lnrFLhyzU7JAoGAXKPEEjWBqBh7NZyIOzoc 25 | 1IZYruqwGVmpxQnwl+ThQ5iYI+EsXqRoUxajosHug3Db2LdYzKzoPLz3oTBXHtjl 26 | e4WRpbTt0Cubu2tupswtaMIRAUYB2w3IxOHkXY7FHTE+3OKd7kF4faJDJ5Tmjd5A 27 | wy+P6ptS0zfps19eklKsJcg= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /clash/tests/data/docker/wg/wg0.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = 192.168.2.2/24 3 | PrivateKey = 2AS8PSccSenWrws5ExglmpwjVBub9Oy9X3zOlk6heHU= 4 | DNS = 1.1.1.1 5 | 6 | [Peer] 7 | PublicKey = MAZPwQBniuXmQf5w8BwM3owlO7Kw07rzyZUXxOvsF3w= 8 | Endpoint = 10.0.0.17:51820 9 | AllowedIPs = 0.0.0.0/0, ::/0 -------------------------------------------------------------------------------- /clash_doc/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash_doc/BUILD.bazel -------------------------------------------------------------------------------- /clash_doc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clash_doc" 3 | repository = { workspace = true } 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | clash_lib = { path = "../clash_lib", version = "*" } -------------------------------------------------------------------------------- /clash_doc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[doc = "docs for clash"] 2 | #[doc(inline)] 3 | pub use clash_lib::ClashConfigDef; 4 | #[doc(inline)] 5 | pub use clash_lib::ClashDNSConfigDef; 6 | -------------------------------------------------------------------------------- /clash_ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clash_ffi" 3 | repository = { workspace = true } 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | 7 | [dependencies] 8 | clash_lib = { path = "../clash_lib", default-features = false, features = ["shadowsocks", "tuic", "ssh", "zero_copy"] } 9 | 10 | [lib] 11 | name = "clashrs" 12 | crate-type = ["staticlib", "cdylib"] 13 | 14 | -------------------------------------------------------------------------------- /clash_ffi/cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | 3 | [export] 4 | include = ["clash_start", "clash_shutdown", "clash_free_string"] 5 | 6 | [parse] 7 | parse_deps = false 8 | include = ["clash_ffi"] -------------------------------------------------------------------------------- /clash_ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | use clash_lib::{Config, Options, TokioRuntime, shutdown, start_scaffold}; 2 | use std::{ 3 | ffi::{CStr, CString}, 4 | os::raw::{c_char, c_int}, 5 | }; 6 | 7 | /// # Safety 8 | /// This function is unsafe because it dereferences raw pointers. 9 | #[unsafe(no_mangle)] 10 | pub unsafe extern "C" fn clash_start( 11 | config: *const c_char, 12 | log: *const c_char, 13 | cwd: *const c_char, 14 | multithread: c_int, 15 | ) -> *mut c_char { 16 | unsafe { 17 | let config_str = CStr::from_ptr(config) 18 | .to_str() 19 | .unwrap_or_default() 20 | .to_string(); 21 | let log_str = CStr::from_ptr(log).to_str().unwrap_or_default().to_string(); 22 | let cwd_str = CStr::from_ptr(cwd).to_str().unwrap_or_default().to_string(); 23 | 24 | let rt = if multithread != 0 { 25 | Some(TokioRuntime::MultiThread) 26 | } else { 27 | Some(TokioRuntime::SingleThread) 28 | }; 29 | 30 | let options = Options { 31 | config: Config::Str(config_str), 32 | cwd: Some(cwd_str), 33 | rt, 34 | log_file: Some(log_str), 35 | }; 36 | 37 | match start_scaffold(options) { 38 | Ok(_) => CString::new("").unwrap().into_raw(), 39 | Err(e) => CString::new(format!("Error: {}", e)).unwrap().into_raw(), 40 | } 41 | } 42 | } 43 | 44 | #[unsafe(no_mangle)] 45 | pub extern "C" fn clash_shutdown() -> c_int { 46 | if shutdown() { 47 | 1 // Success 48 | } else { 49 | 0 // Failure 50 | } 51 | } 52 | 53 | /// # Safety 54 | /// This function is unsafe because it dereferences raw pointers. 55 | #[unsafe(no_mangle)] 56 | #[allow(unused_must_use)] 57 | pub unsafe extern "C" fn clash_free_string(s: *mut c_char) { 58 | if s.is_null() { 59 | return; 60 | } 61 | unsafe { 62 | CString::from_raw(s); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /clash_lib/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /clash_lib/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> std::io::Result<()> { 2 | println!("cargo::rustc-check-cfg=cfg(docker_test)"); 3 | println!("cargo:rerun-if-env-changed=CLASH_DOCKER_TEST"); 4 | if let Some("1" | "true") = option_env!("CLASH_DOCKER_TEST") { 5 | println!("cargo::rustc-cfg=docker_test"); 6 | } 7 | 8 | println!("cargo:rerun-if-changed=src/common/geodata/geodata.proto"); 9 | prost_build::compile_protos( 10 | &["src/common/geodata/geodata.proto"], 11 | &["src/common/geodata"], 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/hello.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use axum::response::IntoResponse; 4 | 5 | pub async fn handle() -> axum::response::Response { 6 | let mut val = HashMap::new(); 7 | val.insert("hello".to_owned(), "clash-rs".to_owned()); 8 | axum::response::Json(val).into_response() 9 | } 10 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/log.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc}; 2 | 3 | use axum::{ 4 | extract::{ConnectInfo, State, WebSocketUpgrade, ws::Message}, 5 | response::IntoResponse, 6 | }; 7 | 8 | use tracing::warn; 9 | 10 | use crate::app::api::AppState; 11 | 12 | pub async fn handle( 13 | ws: WebSocketUpgrade, 14 | ConnectInfo(addr): ConnectInfo, 15 | State(state): State>, 16 | ) -> impl IntoResponse { 17 | ws.on_failed_upgrade(move |e| { 18 | warn!("ws upgrade error: {} with {}", e, addr); 19 | }) 20 | .on_upgrade(move |mut socket| async move { 21 | let mut rx = state.log_source_tx.subscribe(); 22 | while let Ok(evt) = rx.recv().await { 23 | let res = serde_json::to_vec(&evt).unwrap(); 24 | 25 | if let Err(e) = socket 26 | .send(Message::Text(String::from_utf8(res).unwrap().into())) 27 | .await 28 | { 29 | warn!("ws send error: {}", e); 30 | break; 31 | } 32 | } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/memory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::{ 4 | Json, 5 | body::Body, 6 | extract::{FromRequest, Query, Request, State, WebSocketUpgrade, ws::Message}, 7 | response::IntoResponse, 8 | }; 9 | use http::HeaderMap; 10 | use serde::{Deserialize, Serialize}; 11 | use tracing::{debug, warn}; 12 | 13 | use crate::app::api::AppState; 14 | 15 | use super::utils::is_request_websocket; 16 | 17 | #[derive(Deserialize)] 18 | pub struct GetMemoryQuery { 19 | interval: Option, 20 | } 21 | 22 | #[derive(Serialize)] 23 | struct GetMemoryResponse { 24 | inuse: usize, 25 | oslimit: usize, 26 | } 27 | pub async fn handle( 28 | headers: HeaderMap, 29 | State(state): State>, 30 | q: Query, 31 | req: Request, 32 | ) -> impl IntoResponse { 33 | if !is_request_websocket(headers) { 34 | let mgr = state.statistics_manager.clone(); 35 | let snapshot = GetMemoryResponse { 36 | inuse: mgr.memory_usage(), 37 | oslimit: 0, 38 | }; 39 | return Json(snapshot).into_response(); 40 | } 41 | 42 | let ws = match WebSocketUpgrade::from_request(req, &state).await { 43 | Ok(ws) => ws, 44 | Err(e) => { 45 | warn!("ws upgrade error: {}", e); 46 | return e.into_response(); 47 | } 48 | }; 49 | 50 | ws.on_failed_upgrade(|e| { 51 | warn!("ws upgrade error: {}", e); 52 | }) 53 | .on_upgrade(move |mut socket| async move { 54 | let interval = q.interval; 55 | 56 | let mgr = state.statistics_manager.clone(); 57 | 58 | loop { 59 | let snapshot = GetMemoryResponse { 60 | inuse: mgr.memory_usage(), 61 | oslimit: 0, 62 | }; 63 | let j = serde_json::to_vec(&snapshot).unwrap(); 64 | let body = String::from_utf8(j).unwrap(); 65 | 66 | if let Err(e) = socket.send(Message::Text(body.into())).await { 67 | debug!("send memory snapshot failed: {}", e); 68 | break; 69 | } 70 | 71 | tokio::time::sleep(tokio::time::Duration::from_secs( 72 | interval.unwrap_or(1), 73 | )) 74 | .await; 75 | } 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod connection; 3 | pub mod dns; 4 | pub mod hello; 5 | pub mod log; 6 | pub mod memory; 7 | pub mod provider; 8 | pub mod proxy; 9 | pub mod restart; 10 | pub mod rule; 11 | pub mod traffic; 12 | pub mod version; 13 | 14 | mod utils; 15 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/restart.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | use std::os::unix::process::CommandExt; 3 | 4 | use axum::{Json, response::IntoResponse}; 5 | use serde_json::Map; 6 | 7 | pub async fn handle() -> impl IntoResponse { 8 | match std::env::current_exe() { 9 | Ok(exec) => { 10 | let mut map = Map::new(); 11 | map.insert("status".to_owned(), "ok".into()); 12 | tokio::spawn(async move { 13 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 14 | 15 | #[cfg(unix)] 16 | { 17 | use tracing::info; 18 | 19 | let err = std::process::Command::new(exec) 20 | .args(std::env::args().skip(1)) 21 | .envs(std::env::vars()) 22 | .exec(); 23 | info!("process restarted: {}", err); 24 | } 25 | #[cfg(windows)] 26 | { 27 | use tracing::error; 28 | 29 | match std::process::Command::new(exec) 30 | .args(std::env::args().skip(1)) 31 | .envs(std::env::vars()) 32 | .stdin(std::process::Stdio::inherit()) 33 | .stdout(std::process::Stdio::inherit()) 34 | .stderr(std::process::Stdio::inherit()) 35 | .spawn() 36 | { 37 | Ok(_) => { 38 | // exit the current process 39 | std::process::exit(0); 40 | } 41 | Err(e) => { 42 | error!("Failed to restart: {}", e); 43 | } 44 | } 45 | } 46 | }); 47 | Json(map).into_response() 48 | } 49 | Err(e) => { 50 | (http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/rule.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use axum::{Router, extract::State, response::IntoResponse, routing::get}; 4 | 5 | use crate::app::{api::AppState, router::ThreadSafeRouter}; 6 | 7 | #[derive(Clone)] 8 | struct RuleState { 9 | router: ThreadSafeRouter, 10 | } 11 | 12 | pub fn routes(router: ThreadSafeRouter) -> Router> { 13 | Router::new() 14 | .route("/", get(get_rules)) 15 | .with_state(RuleState { router }) 16 | } 17 | 18 | async fn get_rules(State(state): State) -> impl IntoResponse { 19 | let rules = state.router.get_all_rules(); 20 | let mut r = HashMap::new(); 21 | r.insert( 22 | "rules", 23 | rules.iter().map(|r| r.as_map()).collect::>(), 24 | ); 25 | axum::response::Json(r) 26 | } 27 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/traffic.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc}; 2 | 3 | use axum::{ 4 | extract::{ConnectInfo, State, WebSocketUpgrade, ws::Message}, 5 | response::IntoResponse, 6 | }; 7 | 8 | use serde::Serialize; 9 | use tracing::warn; 10 | 11 | use crate::app::api::AppState; 12 | 13 | #[derive(Serialize)] 14 | struct TrafficResponse { 15 | up: u64, 16 | down: u64, 17 | } 18 | pub async fn handle( 19 | ws: WebSocketUpgrade, 20 | ConnectInfo(addr): ConnectInfo, 21 | State(state): State>, 22 | ) -> impl IntoResponse { 23 | ws.on_failed_upgrade(move |e| { 24 | warn!("ws upgrade error: {} with {}", e, addr); 25 | }) 26 | .on_upgrade(move |mut socket| async move { 27 | let mgr = state.statistics_manager.clone(); 28 | loop { 29 | let (up, down) = mgr.now(); 30 | let res = TrafficResponse { up, down }; 31 | let j = serde_json::to_vec(&res).unwrap(); 32 | 33 | if let Err(e) = socket 34 | .send(Message::Text(String::from_utf8(j).unwrap().into())) 35 | .await 36 | { 37 | warn!("ws send error: {}", e); 38 | break; 39 | } 40 | 41 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/utils.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderMap, header}; 2 | 3 | pub fn is_request_websocket(header: HeaderMap) -> bool { 4 | header 5 | .get(header::CONNECTION) 6 | .and_then(|x| x.to_str().ok().map(|x| x.to_ascii_lowercase())) 7 | // Firefox sends "Connection: keep-alive, Upgrade" 8 | .is_some_and(|x| x.contains(&"upgrade".to_ascii_lowercase())) 9 | && header 10 | .get(header::UPGRADE) 11 | .and_then(|x| x.to_str().ok().map(|x| x.to_ascii_lowercase())) 12 | == Some("websocket".to_ascii_lowercase()) 13 | } 14 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/handlers/version.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use axum::response::IntoResponse; 4 | 5 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 6 | 7 | pub async fn handle() -> impl IntoResponse { 8 | let mut val = HashMap::new(); 9 | val.insert("version".to_owned(), VERSION.to_owned()); 10 | axum::response::Json(val) 11 | } 12 | -------------------------------------------------------------------------------- /clash_lib/src/app/api/middlewares/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | -------------------------------------------------------------------------------- /clash_lib/src/app/dispatcher/mod.rs: -------------------------------------------------------------------------------- 1 | mod dispatcher_impl; 2 | mod statistics_manager; 3 | mod tracked; 4 | 5 | pub use dispatcher_impl::Dispatcher; 6 | pub use statistics_manager::Manager as StatisticsManager; 7 | #[allow(unused)] 8 | pub use tracked::{ 9 | BoxedChainedDatagram, BoxedChainedStream, ChainedDatagram, 10 | ChainedDatagramWrapper, ChainedStream, ChainedStreamWrapper, TrackCopy, 11 | TrackedStream, 12 | }; 13 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/fakeip/file_store.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use crate::app::profile::ThreadSafeCacheFile; 4 | 5 | use super::Store; 6 | 7 | pub struct FileStore(ThreadSafeCacheFile); 8 | 9 | impl FileStore { 10 | pub fn new(store: ThreadSafeCacheFile) -> Self { 11 | Self(store) 12 | } 13 | } 14 | 15 | #[async_trait] 16 | impl Store for FileStore { 17 | async fn get_by_host(&mut self, host: &str) -> Option { 18 | self.0 19 | .get_fake_ip(host) 20 | .await 21 | .and_then(|ip| ip.parse().ok()) 22 | } 23 | 24 | async fn pub_by_host(&mut self, host: &str, ip: std::net::IpAddr) { 25 | self.0.set_host_to_ip(host, &ip.to_string()).await; 26 | } 27 | 28 | async fn get_by_ip(&mut self, ip: std::net::IpAddr) -> Option { 29 | self.0.get_fake_ip(&ip.to_string()).await 30 | } 31 | 32 | async fn put_by_ip(&mut self, ip: std::net::IpAddr, host: &str) { 33 | self.0.set_ip_to_host(&ip.to_string(), host).await; 34 | } 35 | 36 | async fn del_by_ip(&mut self, ip: std::net::IpAddr) { 37 | let host = self.get_by_ip(ip).await.unwrap_or_default(); 38 | self.0.delete_fake_ip_pair(&ip.to_string(), &host).await; 39 | } 40 | 41 | async fn exist(&mut self, ip: std::net::IpAddr) -> bool { 42 | self.0.get_fake_ip(&ip.to_string()).await.is_some() 43 | } 44 | 45 | async fn copy_to(&self, #[allow(unused)] store: &mut Box) { 46 | // NO-OP 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/fakeip/mem_store.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | use async_trait::async_trait; 4 | 5 | use super::Store; 6 | 7 | pub struct InMemStore { 8 | itoh: lru_time_cache::LruCache, 9 | htoi: lru_time_cache::LruCache, 10 | } 11 | 12 | impl InMemStore { 13 | pub fn new(size: usize) -> Self { 14 | Self { 15 | itoh: lru_time_cache::LruCache::with_capacity(size), 16 | htoi: lru_time_cache::LruCache::with_capacity(size), 17 | } 18 | } 19 | } 20 | 21 | #[async_trait] 22 | impl Store for InMemStore { 23 | async fn get_by_host(&mut self, host: &str) -> Option { 24 | self.htoi.get_mut(host).map(|ip| { 25 | self.itoh.get_mut(ip); 26 | *ip 27 | }) 28 | } 29 | 30 | async fn pub_by_host(&mut self, host: &str, ip: std::net::IpAddr) { 31 | self.htoi.insert(host.into(), ip); 32 | } 33 | 34 | async fn get_by_ip(&mut self, ip: std::net::IpAddr) -> Option { 35 | self.itoh.get_mut(&ip).map(|h| { 36 | self.htoi.get_mut(h); 37 | h.to_string() 38 | }) 39 | } 40 | 41 | async fn put_by_ip(&mut self, ip: std::net::IpAddr, host: &str) { 42 | self.itoh.insert(ip, host.into()); 43 | } 44 | 45 | async fn del_by_ip(&mut self, ip: std::net::IpAddr) { 46 | if let Some(host) = self.itoh.remove(&ip) { 47 | self.htoi.remove(&host); 48 | } 49 | } 50 | 51 | async fn exist(&mut self, ip: std::net::IpAddr) -> bool { 52 | self.itoh.contains_key(&ip) 53 | } 54 | 55 | async fn copy_to(&self, #[allow(unused)] store: &mut Box) { 56 | // TODO: copy 57 | // NOTE: use file based persistence store 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/filters.rs: -------------------------------------------------------------------------------- 1 | use std::{net, sync::Arc}; 2 | 3 | use crate::common::{mmdb::Mmdb, trie}; 4 | 5 | pub trait FallbackIPFilter: Sync + Send { 6 | fn apply(&self, ip: &net::IpAddr) -> bool; 7 | } 8 | 9 | pub struct GeoIPFilter(String, Arc); 10 | 11 | impl GeoIPFilter { 12 | pub fn new(code: &str, mmdb: Arc) -> Self { 13 | Self(code.to_owned(), mmdb) 14 | } 15 | } 16 | 17 | impl FallbackIPFilter for GeoIPFilter { 18 | fn apply(&self, ip: &net::IpAddr) -> bool { 19 | self.1 20 | .lookup_country(*ip) 21 | .map(|x| x.country) 22 | .is_ok_and(|x| x.is_some_and(|x| x.iso_code == Some(self.0.as_str()))) 23 | } 24 | } 25 | 26 | pub struct IPNetFilter(ipnet::IpNet); 27 | 28 | impl IPNetFilter { 29 | pub fn new(ipnet: ipnet::IpNet) -> Self { 30 | Self(ipnet) 31 | } 32 | } 33 | 34 | impl FallbackIPFilter for IPNetFilter { 35 | fn apply(&self, ip: &net::IpAddr) -> bool { 36 | self.0.contains(ip) 37 | } 38 | } 39 | 40 | pub trait FallbackDomainFilter: Sync + Send { 41 | fn apply(&self, domain: &str) -> bool; 42 | } 43 | 44 | pub struct DomainFilter(trie::StringTrie>); 45 | 46 | impl DomainFilter { 47 | pub fn new(domains: Vec<&str>) -> Self { 48 | let mut f = DomainFilter(trie::StringTrie::new()); 49 | for d in domains { 50 | f.0.insert(d, Arc::new(None)); 51 | } 52 | f 53 | } 54 | } 55 | 56 | impl FallbackDomainFilter for DomainFilter { 57 | fn apply(&self, domain: &str) -> bool { 58 | self.0.search(domain).is_some() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/helper.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::net::{DEFAULT_OUTBOUND_INTERFACE, get_outbound_interface}, 3 | dns::{ 4 | ClashResolver, ThreadSafeDNSClient, 5 | dns_client::{DNSNetMode, DnsClient, Opts}, 6 | }, 7 | }; 8 | use std::sync::Arc; 9 | use tracing::{debug, warn}; 10 | 11 | use super::config::NameServer; 12 | use crate::print_and_exit; 13 | 14 | pub async fn make_clients( 15 | servers: Vec, 16 | resolver: Option>, 17 | ) -> Vec { 18 | let mut rv = Vec::new(); 19 | 20 | for s in servers { 21 | debug!("building nameserver: {}", s); 22 | 23 | let (host, port) = if s.net == DNSNetMode::Dhcp { 24 | (s.address.as_str(), "0") 25 | } else { 26 | let port = s.address.split(':').next_back().unwrap(); 27 | let host = s 28 | .address 29 | .strip_suffix(format!(":{}", port).as_str()) 30 | .unwrap_or_else(|| { 31 | print_and_exit!("invalid address: {}", s.address); 32 | }); 33 | (host, port) 34 | }; 35 | 36 | match DnsClient::new_client(Opts { 37 | r: resolver.as_ref().cloned(), 38 | host: host.to_string(), 39 | port: port.parse::().unwrap_or_else(|_| { 40 | print_and_exit!("invalid port: {}", port); 41 | }), 42 | net: s.net.to_owned(), 43 | iface: s 44 | .interface 45 | .as_ref() 46 | .and_then(|x| match x.as_str() { 47 | "auto" => { 48 | get_outbound_interface().map(|x| x.name.as_str().into()) 49 | } 50 | _ => Some(x.as_str().into()), 51 | }) 52 | .or(DEFAULT_OUTBOUND_INTERFACE 53 | .read() 54 | .await 55 | .as_ref() 56 | .map(|x| x.name.as_str().into())) 57 | .inspect(|x| debug!("DNS client interface: {:?}", x)), 58 | }) 59 | .await 60 | { 61 | Ok(c) => rv.push(c), 62 | Err(e) => warn!("initializing DNS client {} with error {}", &s, e), 63 | } 64 | } 65 | 66 | rv 67 | } 68 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/mod.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use std::fmt::Debug; 4 | 5 | use hickory_proto::op; 6 | use std::sync::Arc; 7 | 8 | #[cfg(test)] 9 | use mockall::automock; 10 | 11 | mod config; 12 | mod dhcp; 13 | mod dns_client; 14 | mod fakeip; 15 | mod filters; 16 | mod helper; 17 | pub mod resolver; 18 | mod runtime; 19 | mod server; 20 | 21 | pub use config::Config; 22 | 23 | pub use resolver::{EnhancedResolver, SystemResolver, new as new_resolver}; 24 | 25 | pub use server::{exchange_with_resolver, get_dns_listener}; 26 | #[async_trait] 27 | pub trait Client: Sync + Send + Debug { 28 | /// used to identify the client for logging 29 | fn id(&self) -> String; 30 | async fn exchange(&self, msg: &op::Message) -> anyhow::Result; 31 | } 32 | 33 | type ThreadSafeDNSClient = Arc; 34 | 35 | pub enum ResolverKind { 36 | Clash, 37 | System, 38 | } 39 | 40 | pub type ThreadSafeDNSResolver = Arc; 41 | 42 | /// A implementation of "anti-poisoning" Resolver 43 | /// it can hold multiple clients in different protocols 44 | /// each client can also hold a "default_resolver" 45 | /// in case they need to resolve DoH in domain names etc. 46 | #[cfg_attr(test, automock)] 47 | #[async_trait] 48 | pub trait ClashResolver: Sync + Send { 49 | async fn resolve( 50 | &self, 51 | host: &str, 52 | enhanced: bool, 53 | ) -> anyhow::Result>; 54 | async fn resolve_v4( 55 | &self, 56 | host: &str, 57 | enhanced: bool, 58 | ) -> anyhow::Result>; 59 | async fn resolve_v6( 60 | &self, 61 | host: &str, 62 | enhanced: bool, 63 | ) -> anyhow::Result>; 64 | 65 | async fn cached_for(&self, ip: std::net::IpAddr) -> Option; 66 | 67 | /// Used for DNS Server 68 | async fn exchange(&self, message: &op::Message) -> anyhow::Result; 69 | 70 | /// Only used for look up fake IP 71 | async fn reverse_lookup(&self, ip: std::net::IpAddr) -> Option; 72 | async fn is_fake_ip(&self, ip: std::net::IpAddr) -> bool; 73 | fn fake_ip_enabled(&self) -> bool; 74 | 75 | fn ipv6(&self) -> bool; 76 | fn set_ipv6(&self, enable: bool); 77 | 78 | fn kind(&self) -> ResolverKind; 79 | } 80 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/resolver/mod.rs: -------------------------------------------------------------------------------- 1 | mod enhanced; 2 | 3 | #[cfg(all(target_feature = "crt-static", target_env = "gnu"))] 4 | #[path = "system_static_crt.rs"] 5 | mod system; 6 | 7 | #[cfg(not(all(target_feature = "crt-static", target_env = "gnu")))] 8 | #[path = "system.rs"] 9 | mod system; 10 | 11 | use std::sync::Arc; 12 | 13 | pub use enhanced::EnhancedResolver; 14 | pub use system::SystemResolver; 15 | 16 | use crate::{app::profile::ThreadSafeCacheFile, common::mmdb::Mmdb, print_and_exit}; 17 | 18 | use super::{Config, ThreadSafeDNSResolver}; 19 | 20 | pub async fn new( 21 | cfg: Config, 22 | store: Option, 23 | mmdb: Option>, 24 | ) -> ThreadSafeDNSResolver { 25 | if cfg.enable { 26 | match (store, mmdb) { 27 | (Some(store), Some(mmdb)) => { 28 | Arc::new(EnhancedResolver::new(cfg, store, mmdb).await) 29 | } 30 | _ => print_and_exit!("enhanced resolver requires cache store and mmdb"), 31 | } 32 | } else { 33 | Arc::new( 34 | SystemResolver::new(cfg.ipv6).expect("failed to create system resolver"), 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, time::Duration}; 2 | 3 | use crate::{ 4 | app::net::Interface, 5 | proxy::utils::{new_tcp_stream, new_udp_socket}, 6 | }; 7 | use hickory_proto::runtime::{ 8 | RuntimeProvider, TokioHandle, TokioTime, iocompat::AsyncIoTokioAsStd, 9 | }; 10 | use tokio::net::UdpSocket as TokioUdpSocket; 11 | 12 | #[derive(Clone)] 13 | pub struct DnsRuntimeProvider { 14 | handle: TokioHandle, 15 | iface: Option, 16 | so_mark: Option, 17 | } 18 | 19 | impl DnsRuntimeProvider { 20 | pub fn new(iface: Option, so_mark: Option) -> Self { 21 | Self { 22 | handle: TokioHandle::default(), 23 | iface, 24 | so_mark, 25 | } 26 | } 27 | } 28 | 29 | impl RuntimeProvider for DnsRuntimeProvider { 30 | type Handle = TokioHandle; 31 | type Tcp = AsyncIoTokioAsStd; 32 | type Timer = TokioTime; 33 | type Udp = TokioUdpSocket; 34 | 35 | fn create_handle(&self) -> Self::Handle { 36 | self.handle.clone() 37 | } 38 | 39 | // TODO: move this timeout into new_tcp_stream 40 | fn connect_tcp( 41 | &self, 42 | server_addr: SocketAddr, 43 | // ignored: self.iface is taken 44 | _bind_addr: Option, 45 | _timeout: Option, 46 | ) -> std::pin::Pin< 47 | Box< 48 | dyn Send 49 | + std::prelude::rust_2024::Future>, 50 | >, 51 | > { 52 | let iface = self.iface.clone(); 53 | let _so_mark = self.so_mark; 54 | Box::pin(async move { 55 | new_tcp_stream( 56 | server_addr, 57 | iface, 58 | #[cfg(target_os = "linux")] 59 | _so_mark, 60 | ) 61 | .await 62 | .map(AsyncIoTokioAsStd) 63 | }) 64 | } 65 | 66 | fn bind_udp( 67 | &self, 68 | _local_addr: SocketAddr, 69 | _server_addr: SocketAddr, 70 | ) -> std::pin::Pin< 71 | Box< 72 | dyn Send 73 | + std::prelude::rust_2024::Future>, 74 | >, 75 | > { 76 | let iface = self.iface.clone(); 77 | let _so_mark = self.so_mark; 78 | Box::pin(async move { 79 | new_udp_socket( 80 | None, 81 | iface.clone(), 82 | #[cfg(target_os = "linux")] 83 | _so_mark, 84 | ) 85 | .await 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /clash_lib/src/app/dns/server/mod.rs: -------------------------------------------------------------------------------- 1 | use hickory_proto::op::Message; 2 | 3 | use tracing::error; 4 | use watfaq_dns::DNSListenAddr; 5 | 6 | use crate::Runner; 7 | 8 | use super::ThreadSafeDNSResolver; 9 | 10 | mod handler; 11 | pub use handler::exchange_with_resolver; 12 | 13 | static DEFAULT_DNS_SERVER_TTL: u32 = 60; 14 | 15 | struct DnsMessageExchanger { 16 | resolver: ThreadSafeDNSResolver, 17 | } 18 | 19 | impl watfaq_dns::DnsMessageExchanger for DnsMessageExchanger { 20 | fn ipv6(&self) -> bool { 21 | self.resolver.ipv6() 22 | } 23 | 24 | async fn exchange( 25 | &self, 26 | message: &Message, 27 | ) -> Result { 28 | exchange_with_resolver(&self.resolver, message, true).await 29 | } 30 | } 31 | 32 | pub async fn get_dns_listener( 33 | listen: DNSListenAddr, 34 | resolver: ThreadSafeDNSResolver, 35 | cwd: &std::path::Path, 36 | ) -> Option { 37 | let h = DnsMessageExchanger { resolver }; 38 | let r = watfaq_dns::get_dns_listener(listen, h, cwd).await; 39 | match r { 40 | Some(r) => Some(Box::pin(async move { 41 | match r.await { 42 | Ok(()) => Ok(()), 43 | Err(err) => { 44 | error!("dns listener error: {}", err); 45 | Err(err.into()) 46 | } 47 | } 48 | })), 49 | _ => None, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /clash_lib/src/app/inbound/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | pub mod network_listener; 3 | -------------------------------------------------------------------------------- /clash_lib/src/app/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod dispatcher; 3 | pub mod dns; 4 | pub mod inbound; 5 | pub mod logging; 6 | pub mod net; 7 | pub mod outbound; 8 | pub mod profile; 9 | pub mod remote_content_manager; 10 | pub mod router; 11 | -------------------------------------------------------------------------------- /clash_lib/src/app/outbound/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | 3 | mod utils; 4 | -------------------------------------------------------------------------------- /clash_lib/src/app/remote_content_manager/providers/file_vehicle.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::fs; 3 | 4 | use super::{ProviderVehicle, ProviderVehicleType}; 5 | 6 | pub struct Vehicle { 7 | path: String, 8 | } 9 | 10 | impl Vehicle { 11 | pub fn new(path: &str) -> Self { 12 | Self { path: path.into() } 13 | } 14 | } 15 | 16 | #[async_trait] 17 | impl ProviderVehicle for Vehicle { 18 | async fn read(&self) -> std::io::Result> { 19 | fs::read(&self.path) 20 | } 21 | 22 | fn path(&self) -> &str { 23 | self.path.as_str() 24 | } 25 | 26 | fn typ(&self) -> ProviderVehicleType { 27 | ProviderVehicleType::File 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /clash_lib/src/app/remote_content_manager/providers/mod.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use erased_serde::Serialize; 3 | use serde::Deserialize; 4 | use std::{ 5 | collections::HashMap, 6 | fmt::{Display, Formatter}, 7 | io, 8 | sync::Arc, 9 | }; 10 | 11 | pub mod fetcher; 12 | pub mod file_vehicle; 13 | pub mod http_vehicle; 14 | pub mod proxy_provider; 15 | pub mod rule_provider; 16 | 17 | #[cfg(test)] 18 | use mockall::automock; 19 | 20 | #[derive(Deserialize, PartialEq, Clone, Copy, Debug)] 21 | pub enum ProviderVehicleType { 22 | File, 23 | Http, 24 | Compatible, 25 | } 26 | 27 | impl Display for ProviderVehicleType { 28 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 29 | match self { 30 | ProviderVehicleType::File => write!(f, "File"), 31 | ProviderVehicleType::Http => write!(f, "HTTP"), 32 | ProviderVehicleType::Compatible => write!(f, "Compatible"), 33 | } 34 | } 35 | } 36 | 37 | pub type ThreadSafeProviderVehicle = Arc; 38 | 39 | #[cfg_attr(test, automock)] 40 | #[async_trait] 41 | pub trait ProviderVehicle { 42 | async fn read(&self) -> io::Result>; 43 | fn path(&self) -> &str; 44 | fn typ(&self) -> ProviderVehicleType; 45 | } 46 | 47 | pub enum ProviderType { 48 | Proxy, 49 | Rule, 50 | } 51 | impl Display for ProviderType { 52 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 53 | match self { 54 | ProviderType::Proxy => write!(f, "Proxy"), 55 | ProviderType::Rule => write!(f, "Rule"), 56 | } 57 | } 58 | } 59 | 60 | /// either Proxy or Rule provider 61 | #[async_trait] 62 | pub trait Provider { 63 | fn name(&self) -> &str; 64 | fn vehicle_type(&self) -> ProviderVehicleType; 65 | fn typ(&self) -> ProviderType; 66 | async fn initialize(&self) -> io::Result<()>; 67 | async fn update(&self) -> io::Result<()>; 68 | 69 | async fn as_map(&self) -> HashMap>; 70 | } 71 | -------------------------------------------------------------------------------- /clash_lib/src/app/remote_content_manager/providers/proxy_provider/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod plain_provider; 2 | 3 | pub mod proxy_set_provider; 4 | 5 | pub use plain_provider::PlainProvider; 6 | pub use proxy_set_provider::ProxySetProvider; 7 | 8 | use std::sync::Arc; 9 | 10 | use async_trait::async_trait; 11 | use tokio::sync::RwLock; 12 | 13 | use crate::{ 14 | app::remote_content_manager::providers::Provider, proxy::AnyOutboundHandler, 15 | }; 16 | 17 | pub type ThreadSafeProxyProvider = Arc>; 18 | 19 | #[async_trait] 20 | pub trait ProxyProvider: Provider { 21 | async fn proxies(&self) -> Vec; 22 | async fn touch(&self); 23 | /// this is a blocking call, you may want to spawn a new task to run this 24 | async fn healthcheck(&self); 25 | } 26 | -------------------------------------------------------------------------------- /clash_lib/src/app/remote_content_manager/providers/rule_provider/cidr_trie.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 2 | 3 | use ip_network_table_deps_treebitmap::IpLookupTable; 4 | 5 | pub struct CidrTrie { 6 | v4: IpLookupTable, 7 | v6: IpLookupTable, 8 | } 9 | 10 | impl CidrTrie { 11 | pub fn new() -> Self { 12 | Self { 13 | v4: IpLookupTable::new(), 14 | v6: IpLookupTable::new(), 15 | } 16 | } 17 | 18 | pub fn insert(&mut self, cidr: &str) -> bool { 19 | if let Ok(cidr) = cidr.parse::() { 20 | match cidr { 21 | ipnet::IpNet::V4(v4) => { 22 | self.v4.insert(v4.addr(), v4.prefix_len() as _, true); 23 | true 24 | } 25 | ipnet::IpNet::V6(v6) => { 26 | self.v6.insert(v6.addr(), v6.prefix_len() as _, true); 27 | true 28 | } 29 | } 30 | } else { 31 | false 32 | } 33 | } 34 | 35 | pub fn contains(&self, ip: IpAddr) -> bool { 36 | match ip { 37 | IpAddr::V4(v4) => self.v4.longest_match(v4).is_some(), 38 | IpAddr::V6(v6) => self.v6.longest_match(v6).is_some(), 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /clash_lib/src/app/remote_content_manager/providers/rule_provider/mod.rs: -------------------------------------------------------------------------------- 1 | mod cidr_trie; 2 | mod mrs; 3 | mod provider; 4 | 5 | pub use provider::{ 6 | RuleProviderImpl, RuleSetBehavior, RuleSetFormat, ThreadSafeRuleProvider, 7 | }; 8 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/domain.rs: -------------------------------------------------------------------------------- 1 | use crate::session; 2 | 3 | use super::RuleMatcher; 4 | 5 | #[derive(Clone)] 6 | pub struct Domain { 7 | pub domain: String, 8 | pub target: String, 9 | } 10 | 11 | impl std::fmt::Display for Domain { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!(f, "{} domain {}", self.target, self.domain) 14 | } 15 | } 16 | 17 | impl RuleMatcher for Domain { 18 | fn apply(&self, sess: &session::Session) -> bool { 19 | match &sess.destination { 20 | session::SocksAddr::Ip(_) => false, 21 | session::SocksAddr::Domain(domain, _) => &self.domain == domain, 22 | } 23 | } 24 | 25 | fn target(&self) -> &str { 26 | &self.target 27 | } 28 | 29 | fn payload(&self) -> String { 30 | self.domain.clone() 31 | } 32 | 33 | fn type_name(&self) -> &str { 34 | "Domain" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/domain_keyword.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::session; 4 | 5 | use super::RuleMatcher; 6 | 7 | #[derive(Clone)] 8 | pub struct DomainKeyword { 9 | pub keyword: String, 10 | pub target: String, 11 | } 12 | 13 | impl Display for DomainKeyword { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | write!(f, "{} keyword {}", self.target, self.keyword) 16 | } 17 | } 18 | 19 | impl RuleMatcher for DomainKeyword { 20 | fn apply(&self, sess: &session::Session) -> bool { 21 | match &sess.destination { 22 | session::SocksAddr::Ip(_) => false, 23 | session::SocksAddr::Domain(domain, _) => domain.contains(&self.keyword), 24 | } 25 | } 26 | 27 | fn target(&self) -> &str { 28 | &self.target 29 | } 30 | 31 | fn payload(&self) -> String { 32 | self.keyword.to_owned() 33 | } 34 | 35 | fn type_name(&self) -> &str { 36 | "DomainKeyword" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/domain_regex.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::router::rules::RuleMatcher, 3 | session::{Session, SocksAddr}, 4 | }; 5 | 6 | #[derive(Clone)] 7 | pub struct DomainRegex { 8 | pub regex: regex::Regex, 9 | pub target: String, 10 | } 11 | 12 | impl std::fmt::Display for DomainRegex { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | write!(f, "{} suffix {}", self.target, self.regex) 15 | } 16 | } 17 | 18 | impl RuleMatcher for DomainRegex { 19 | fn apply(&self, sess: &Session) -> bool { 20 | match &sess.destination { 21 | SocksAddr::Ip(_) => false, 22 | SocksAddr::Domain(domain, _) => self.regex.is_match(domain), 23 | } 24 | } 25 | 26 | fn target(&self) -> &str { 27 | self.target.as_str() 28 | } 29 | 30 | fn payload(&self) -> String { 31 | self.regex.to_string() 32 | } 33 | 34 | fn type_name(&self) -> &str { 35 | "DomainRegex" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/domain_suffix.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::router::rules::RuleMatcher, 3 | session::{Session, SocksAddr}, 4 | }; 5 | 6 | #[derive(Clone)] 7 | pub struct DomainSuffix { 8 | pub suffix: String, 9 | pub target: String, 10 | } 11 | 12 | impl std::fmt::Display for DomainSuffix { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | write!(f, "{} suffix {}", self.target, self.suffix) 15 | } 16 | } 17 | 18 | impl RuleMatcher for DomainSuffix { 19 | fn apply(&self, sess: &Session) -> bool { 20 | match &sess.destination { 21 | SocksAddr::Ip(_) => false, 22 | SocksAddr::Domain(domain, _) => { 23 | domain.ends_with((String::from(".") + self.suffix.as_str()).as_str()) 24 | || domain == &self.suffix 25 | } 26 | } 27 | } 28 | 29 | fn target(&self) -> &str { 30 | self.target.as_str() 31 | } 32 | 33 | fn payload(&self) -> String { 34 | self.suffix.clone() 35 | } 36 | 37 | fn type_name(&self) -> &str { 38 | "DomainSuffix" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/final_.rs: -------------------------------------------------------------------------------- 1 | use crate::{app::router::rules::RuleMatcher, session::Session}; 2 | 3 | #[derive(Clone)] 4 | pub struct Final { 5 | pub target: String, 6 | } 7 | 8 | impl std::fmt::Display for Final { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | write!(f, "{} final", self.target) 11 | } 12 | } 13 | 14 | impl RuleMatcher for Final { 15 | fn apply(&self, _sess: &Session) -> bool { 16 | true 17 | } 18 | 19 | fn target(&self) -> &str { 20 | self.target.as_str() 21 | } 22 | 23 | fn payload(&self) -> String { 24 | "".to_owned() 25 | } 26 | 27 | fn type_name(&self) -> &str { 28 | "Match" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/geodata/attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::common::geodata::geodata_proto; 2 | 3 | pub trait AttrMatcher { 4 | fn matches(&self, domain: &geodata_proto::Domain) -> bool; 5 | } 6 | 7 | pub struct BooleanAttrMatcher(pub String); 8 | 9 | impl AttrMatcher for BooleanAttrMatcher { 10 | fn matches(&self, domain: &geodata_proto::Domain) -> bool { 11 | for attr in &domain.attribute { 12 | if attr.key.eq_ignore_ascii_case(&self.0) { 13 | return true; 14 | } 15 | } 16 | false 17 | } 18 | } 19 | 20 | impl From for BooleanAttrMatcher { 21 | fn from(s: String) -> Self { 22 | BooleanAttrMatcher(s) 23 | } 24 | } 25 | 26 | // logical AND of multiple attribute matchers 27 | pub struct AndAttrMatcher { 28 | list: Vec>, 29 | } 30 | 31 | impl From> for AndAttrMatcher { 32 | fn from(list: Vec) -> Self { 33 | AndAttrMatcher { 34 | list: list 35 | .into_iter() 36 | .map(|s| Box::new(BooleanAttrMatcher(s)) as Box) 37 | .collect(), 38 | } 39 | } 40 | } 41 | 42 | impl AttrMatcher for AndAttrMatcher { 43 | fn matches(&self, domain: &geodata_proto::Domain) -> bool { 44 | for matcher in &self.list { 45 | if !matcher.matches(domain) { 46 | return false; 47 | } 48 | } 49 | true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/geodata/matcher_group.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::router::rules::geodata::str_matcher::{Matcher, try_new_matcher}, 3 | common::{ 4 | geodata::geodata_proto::{Domain, domain::Type}, 5 | trie, 6 | }, 7 | }; 8 | use std::sync::Arc; 9 | 10 | pub trait DomainGroupMatcher: Send + Sync { 11 | fn apply(&self, domain: &str) -> bool; 12 | } 13 | 14 | pub struct SuccinctMatcherGroup { 15 | set: trie::StringTrie<()>, 16 | other_matchers: Vec>, 17 | not: bool, 18 | } 19 | 20 | impl SuccinctMatcherGroup { 21 | pub fn try_new(domains: Vec, not: bool) -> Result { 22 | let mut set = trie::StringTrie::new(); 23 | let mut other_matchers = Vec::new(); 24 | for domain in domains { 25 | let t = Type::try_from(domain.r#type).map_err(|x| { 26 | crate::Error::InvalidConfig(format!("invalid domain type: {}", x)) 27 | })?; 28 | 29 | match t { 30 | Type::Plain | Type::Regex => { 31 | let matcher = try_new_matcher(domain.value, t)?; 32 | other_matchers.push(matcher); 33 | } 34 | Type::Domain => { 35 | let domain = format!("+.{}", domain.value); 36 | set.insert(&domain, Arc::new(())); 37 | } 38 | Type::Full => { 39 | set.insert(&domain.value, Arc::new(())); 40 | } 41 | } 42 | } 43 | Ok(SuccinctMatcherGroup { 44 | set, 45 | other_matchers, 46 | not, 47 | }) 48 | } 49 | } 50 | 51 | impl DomainGroupMatcher for SuccinctMatcherGroup { 52 | fn apply(&self, domain: &str) -> bool { 53 | let mut is_matched = self.set.search(domain).is_some(); 54 | if !is_matched { 55 | for matcher in &self.other_matchers { 56 | if matcher.matches(domain) { 57 | is_matched = true; 58 | break; 59 | } 60 | } 61 | } 62 | if self.not { !is_matched } else { is_matched } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/geoip.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use tracing::debug; 4 | 5 | use crate::{common::mmdb, session::Session}; 6 | 7 | use super::RuleMatcher; 8 | 9 | #[derive(Clone)] 10 | pub struct GeoIP { 11 | pub target: String, 12 | pub country_code: String, 13 | pub no_resolve: bool, 14 | pub mmdb: Arc, 15 | } 16 | 17 | impl std::fmt::Display for GeoIP { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | write!(f, "GeoIP({} - {})", self.target, self.country_code) 20 | } 21 | } 22 | 23 | impl RuleMatcher for GeoIP { 24 | fn apply(&self, sess: &Session) -> bool { 25 | let ip = sess.resolved_ip.or(sess.destination.ip()); 26 | 27 | if let Some(ip) = ip { 28 | match self.mmdb.lookup_country(ip) { 29 | Ok(country) => { 30 | country 31 | .country 32 | .map(|x| x.iso_code) 33 | .unwrap_or_default() 34 | .unwrap_or_default() 35 | == self.country_code 36 | } 37 | Err(e) => { 38 | debug!("GeoIP lookup failed: {}", e); 39 | false 40 | } 41 | } 42 | } else { 43 | false 44 | } 45 | } 46 | 47 | fn target(&self) -> &str { 48 | self.target.as_str() 49 | } 50 | 51 | fn should_resolve_ip(&self) -> bool { 52 | !self.no_resolve 53 | } 54 | 55 | fn payload(&self) -> String { 56 | self.country_code.clone() 57 | } 58 | 59 | fn type_name(&self) -> &str { 60 | "GeoIP" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/ipcidr.rs: -------------------------------------------------------------------------------- 1 | use crate::{app::router::rules::RuleMatcher, session::Session}; 2 | 3 | #[derive(Clone)] 4 | pub struct IpCidr { 5 | pub ipnet: ipnet::IpNet, 6 | pub target: String, 7 | pub match_src: bool, 8 | pub no_resolve: bool, 9 | } 10 | 11 | impl std::fmt::Display for IpCidr { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | write!( 14 | f, 15 | "{} {} {}", 16 | self.target, 17 | if self.match_src { "src" } else { "dst" }, 18 | self.ipnet 19 | ) 20 | } 21 | } 22 | 23 | impl RuleMatcher for IpCidr { 24 | fn apply(&self, sess: &Session) -> bool { 25 | if self.match_src { 26 | self.ipnet.contains(&sess.source.ip()) 27 | } else { 28 | let ip = sess.resolved_ip.or(sess.destination.ip()); 29 | 30 | if let Some(ip) = ip { 31 | self.ipnet.contains(&ip) 32 | } else { 33 | false 34 | } 35 | } 36 | } 37 | 38 | fn target(&self) -> &str { 39 | self.target.as_str() 40 | } 41 | 42 | fn should_resolve_ip(&self) -> bool { 43 | !self.no_resolve 44 | } 45 | 46 | fn payload(&self) -> String { 47 | self.ipnet.to_string() 48 | } 49 | 50 | fn type_name(&self) -> &str { 51 | "IPCIDR" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Display}; 2 | 3 | use erased_serde::Serialize; 4 | 5 | use crate::session::Session; 6 | 7 | pub mod domain; 8 | pub mod domain_keyword; 9 | pub mod domain_regex; 10 | pub mod domain_suffix; 11 | pub mod final_; 12 | pub mod geodata; 13 | pub mod geoip; 14 | pub mod ipcidr; 15 | pub mod port; 16 | pub mod process; 17 | pub mod ruleset; 18 | 19 | pub trait RuleMatcher: Send + Sync + Unpin + Display { 20 | /// check if the rule should apply to the session 21 | fn apply(&self, sess: &Session) -> bool; 22 | 23 | /// the Proxy to use 24 | fn target(&self) -> &str; 25 | 26 | /// the actual content of the rule 27 | fn payload(&self) -> String; 28 | 29 | /// the type of the rule 30 | fn type_name(&self) -> &str; 31 | 32 | fn should_resolve_ip(&self) -> bool { 33 | false 34 | } 35 | 36 | fn as_map(&self) -> HashMap> { 37 | let mut m: HashMap> = HashMap::new(); 38 | m.insert("type".to_string(), Box::new(self.type_name().to_owned())); 39 | m.insert("proxy".to_string(), Box::new(self.target().to_owned())); 40 | m.insert("payload".to_string(), Box::new(self.payload().to_owned())); 41 | m 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/port.rs: -------------------------------------------------------------------------------- 1 | use crate::{app::router::rules::RuleMatcher, session::Session}; 2 | 3 | #[derive(Clone)] 4 | pub struct Port { 5 | pub port: u16, 6 | pub target: String, 7 | pub is_src: bool, 8 | } 9 | 10 | impl std::fmt::Display for Port { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!( 13 | f, 14 | "{} {} port {}", 15 | self.target, 16 | if self.is_src { "src" } else { "dst" }, 17 | self.port 18 | ) 19 | } 20 | } 21 | 22 | impl RuleMatcher for Port { 23 | fn apply(&self, sess: &Session) -> bool { 24 | if self.is_src { 25 | sess.source.port() == self.port 26 | } else { 27 | sess.destination.port() == self.port 28 | } 29 | } 30 | 31 | fn target(&self) -> &str { 32 | self.target.as_str() 33 | } 34 | 35 | fn payload(&self) -> String { 36 | self.port.to_string() 37 | } 38 | 39 | fn type_name(&self) -> &str { 40 | "Port" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/process.rs: -------------------------------------------------------------------------------- 1 | use super::RuleMatcher; 2 | 3 | pub struct Process { 4 | pub name: String, 5 | pub target: String, 6 | #[allow(dead_code)] 7 | pub name_only: bool, 8 | } 9 | 10 | impl std::fmt::Display for Process { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "{} process {}", self.target, self.name) 13 | } 14 | } 15 | 16 | impl RuleMatcher for Process { 17 | fn apply(&self, _sess: &crate::session::Session) -> bool { 18 | // TODO: implement this 19 | false 20 | } 21 | 22 | fn target(&self) -> &str { 23 | &self.target 24 | } 25 | 26 | fn payload(&self) -> String { 27 | self.name.clone() 28 | } 29 | 30 | fn type_name(&self) -> &str { 31 | "Process" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /clash_lib/src/app/router/rules/ruleset.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::{ 3 | remote_content_manager::providers::rule_provider::ThreadSafeRuleProvider, 4 | router::rules::RuleMatcher, 5 | }, 6 | session::Session, 7 | }; 8 | 9 | #[derive(Clone)] 10 | pub struct RuleSet { 11 | pub rule_set: String, 12 | pub target: String, 13 | pub rule_provider: ThreadSafeRuleProvider, 14 | } 15 | 16 | impl RuleSet { 17 | pub fn new( 18 | rule_set: String, 19 | target: String, 20 | rule_provider: ThreadSafeRuleProvider, 21 | ) -> Self { 22 | Self { 23 | rule_set, 24 | target, 25 | rule_provider, 26 | } 27 | } 28 | } 29 | 30 | impl std::fmt::Display for RuleSet { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | write!(f, "{} rule-set {}", self.target, self.rule_set) 33 | } 34 | } 35 | 36 | impl RuleMatcher for RuleSet { 37 | fn apply(&self, sess: &Session) -> bool { 38 | self.rule_provider.search(sess) 39 | } 40 | 41 | fn target(&self) -> &str { 42 | self.target.as_str() 43 | } 44 | 45 | fn payload(&self) -> String { 46 | self.rule_set.clone() 47 | } 48 | 49 | fn type_name(&self) -> &str { 50 | "RuleSet" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /clash_lib/src/common/auth.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | pub trait Authenticator { 4 | fn authenticate(&self, username: &str, password: &str) -> bool; 5 | #[allow(unused)] 6 | fn users(&self) -> Vec; 7 | fn enabled(&self) -> bool; 8 | } 9 | 10 | pub type ThreadSafeAuthenticator = Arc; 11 | 12 | pub struct User(String, String); 13 | 14 | impl User { 15 | pub fn new(username: String, password: String) -> Self { 16 | Self(username, password) 17 | } 18 | } 19 | 20 | pub struct PlainAuthenticator { 21 | store: HashMap, 22 | usernames: Vec, 23 | } 24 | 25 | impl PlainAuthenticator { 26 | pub fn new(users: Vec) -> Self { 27 | let mut store = HashMap::new(); 28 | let mut usernames = Vec::new(); 29 | for user in users { 30 | store.insert(user.0.clone(), user.1.clone()); 31 | usernames.push(user.0.clone()); 32 | } 33 | Self { store, usernames } 34 | } 35 | } 36 | 37 | impl Authenticator for PlainAuthenticator { 38 | fn authenticate(&self, username: &str, password: &str) -> bool { 39 | match self.store.get(username) { 40 | Some(p) => p == password, 41 | None => false, 42 | } 43 | } 44 | 45 | fn users(&self) -> Vec { 46 | self.usernames.clone() 47 | } 48 | 49 | fn enabled(&self) -> bool { 50 | !self.usernames.is_empty() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /clash_lib/src/common/defer.rs: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/29963675/1109167 2 | pub struct ScopeCall { 3 | pub c: Option, 4 | } 5 | impl Drop for ScopeCall { 6 | fn drop(&mut self) { 7 | self.c.take().unwrap()() 8 | } 9 | } 10 | 11 | #[macro_export] 12 | macro_rules! expr { 13 | ($e:expr_2021) => { 14 | $e 15 | }; 16 | } // tt hack 17 | 18 | #[macro_export] 19 | macro_rules! defer { 20 | ($($data: tt)*) => ( 21 | let _scope_call = $crate::common::defer::ScopeCall { 22 | c: Some(|| -> () { $crate::expr!({ $($data)* }) }) 23 | }; 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /clash_lib/src/common/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub fn new_io_error(msg: T) -> io::Error 4 | where 5 | T: Into>, 6 | { 7 | io::Error::new(io::ErrorKind::Other, msg.into()) 8 | } 9 | 10 | pub fn map_io_error(err: T) -> io::Error 11 | where 12 | T: Into + Send, 13 | { 14 | io::Error::new(io::ErrorKind::Other, format!("{:?}", anyhow::anyhow!(err))) 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! print_and_exit { 19 | ($($arg:tt)*) => {{ 20 | eprintln!($($arg)*); 21 | std::process::exit(1); 22 | }}; 23 | } 24 | -------------------------------------------------------------------------------- /clash_lib/src/common/geodata/geodata.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package geodata; 4 | 5 | // Domain for routing decision. 6 | message Domain { 7 | // Type of domain value. 8 | enum Type { 9 | // The value is used as is. 10 | Plain = 0; 11 | // The value is used as a regular expression. 12 | Regex = 1; 13 | // The value is a root domain. 14 | Domain = 2; 15 | // The value is a domain. 16 | Full = 3; 17 | } 18 | 19 | // Domain matching type. 20 | Type type = 1; 21 | 22 | // Domain value. 23 | string value = 2; 24 | 25 | message Attribute { 26 | string key = 1; 27 | 28 | oneof typed_value { 29 | bool bool_value = 2; 30 | int64 int_value = 3; 31 | } 32 | } 33 | 34 | // Attributes of this domain. May be used for filtering. 35 | repeated Attribute attribute = 3; 36 | } 37 | 38 | // IP for routing decision, in CIDR form. 39 | message CIDR { 40 | // IP address, should be either 4 or 16 bytes. 41 | bytes ip = 1; 42 | 43 | // Number of leading ones in the network mask. 44 | uint32 prefix = 2; 45 | } 46 | 47 | message GeoIP { 48 | string country_code = 1; 49 | repeated CIDR cidr = 2; 50 | bool reverse_match = 3; 51 | } 52 | 53 | message GeoIPList { 54 | repeated GeoIP entry = 1; 55 | } 56 | 57 | message GeoSite { 58 | string country_code = 1; 59 | repeated Domain domain = 2; 60 | } 61 | 62 | message GeoSiteList { 63 | repeated GeoSite entry = 1; 64 | } -------------------------------------------------------------------------------- /clash_lib/src/common/geodata/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, common::utils::download}; 2 | use prost::Message; 3 | use std::path::Path; 4 | use tracing::{debug, info}; 5 | 6 | use super::http::HttpClient; 7 | 8 | pub(crate) mod geodata_proto { 9 | include!(concat!(env!("OUT_DIR"), "/geodata.rs")); 10 | } 11 | 12 | pub struct GeoData { 13 | cache: geodata_proto::GeoSiteList, 14 | } 15 | 16 | impl GeoData { 17 | pub async fn new>( 18 | path: P, 19 | download_url: Option, 20 | http_client: HttpClient, 21 | ) -> Result { 22 | debug!("geosite path: {}", path.as_ref().to_string_lossy()); 23 | 24 | let geosite_file = path.as_ref().to_path_buf(); 25 | 26 | if !geosite_file.exists() { 27 | if let Some(url) = download_url.as_ref() { 28 | info!("downloading geodata from {}", url); 29 | download(url, &geosite_file, &http_client) 30 | .await 31 | .map_err(|x| { 32 | Error::InvalidConfig(format!( 33 | "geosite download failed: {}", 34 | x 35 | )) 36 | })?; 37 | } else { 38 | return Err(Error::InvalidConfig(format!( 39 | "geosite `{}` not found and geosite_download_url is not set", 40 | path.as_ref().to_string_lossy() 41 | ))); 42 | } 43 | } 44 | let bytes = tokio::fs::read(path).await?; 45 | let cache = 46 | geodata_proto::GeoSiteList::decode(bytes.as_slice()).map_err(|x| { 47 | Error::InvalidConfig(format!("geosite decode failed: {}", x)) 48 | })?; 49 | Ok(Self { cache }) 50 | } 51 | 52 | #[cfg(test)] 53 | pub async fn from_file>(path: P) -> Result { 54 | let bytes = tokio::fs::read(path).await?; 55 | let cache = 56 | geodata_proto::GeoSiteList::decode(bytes.as_slice()).map_err(|x| { 57 | Error::InvalidConfig(format!("geosite decode failed: {}", x)) 58 | })?; 59 | Ok(Self { cache }) 60 | } 61 | 62 | pub fn get(&self, list: &str) -> Option<&geodata_proto::GeoSite> { 63 | self.cache 64 | .entry 65 | .iter() 66 | .find(|x| x.country_code.eq_ignore_ascii_case(list)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /clash_lib/src/common/http/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod hyper; 3 | 4 | pub use client::*; 5 | pub use hyper::HyperResponseBody; 6 | -------------------------------------------------------------------------------- /clash_lib/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod crypto; 3 | pub mod defer; 4 | pub mod errors; 5 | pub mod geodata; 6 | pub mod http; 7 | pub mod io; 8 | pub mod mmdb; 9 | pub mod succinct_set; 10 | pub mod timed_future; 11 | pub mod tls; 12 | pub mod trie; 13 | pub mod utils; 14 | -------------------------------------------------------------------------------- /clash_lib/src/common/timed_future.rs: -------------------------------------------------------------------------------- 1 | use std::{pin::Pin, time::Duration}; 2 | 3 | use futures::Future; 4 | use tokio::time::Instant; 5 | 6 | pub struct TimedFuture { 7 | fut: Fut, 8 | started_at: Option, 9 | } 10 | 11 | impl TimedFuture { 12 | pub fn new(fut: Fut, started_at: Option) -> Self { 13 | Self { fut, started_at } 14 | } 15 | } 16 | 17 | impl Future for TimedFuture { 18 | type Output = (Fut::Output, Duration); 19 | 20 | fn poll( 21 | self: std::pin::Pin<&mut Self>, 22 | cx: &mut std::task::Context<'_>, 23 | ) -> std::task::Poll { 24 | let Self { fut, started_at } = self.get_mut(); 25 | let started_at = started_at.get_or_insert_with(Instant::now); 26 | let output = futures::ready!(Pin::new(fut).poll(cx)); 27 | let elapsed = started_at.elapsed(); 28 | std::task::Poll::Ready((output, elapsed)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /clash_lib/src/config/internal/convert/general.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | use crate::{ 4 | app::net::Interface, 5 | config::{ 6 | config::{Controller, General}, 7 | def, 8 | }, 9 | }; 10 | 11 | pub(super) fn convert(c: &def::Config) -> Result { 12 | Ok(General { 13 | authentication: c.authentication.clone(), 14 | controller: Controller { 15 | external_controller: c.external_controller.clone(), 16 | external_ui: c.external_ui.clone(), 17 | secret: c.secret.clone(), 18 | cors_allow_origins: c.cors_allow_origins.clone(), 19 | }, 20 | mode: c.mode, 21 | log_level: c.log_level, 22 | ipv6: c.ipv6, 23 | interface: c.interface.as_ref().map(|iface| { 24 | if let Ok(addr) = iface.parse::() { 25 | Interface::IpAddr(addr) 26 | } else { 27 | Interface::Name(iface.to_string()) 28 | } 29 | }), 30 | routing_mask: c.routing_mask, 31 | mmdb: c.mmdb.to_owned(), 32 | mmdb_download_url: c.mmdb_download_url.to_owned(), 33 | asn_mmdb: c.asn_mmdb.to_owned(), 34 | asn_mmdb_download_url: c.asn_mmdb_download_url.to_owned(), 35 | geosite: c.geosite.to_owned(), 36 | geosite_download_url: c.geosite_download_url.to_owned(), 37 | bind_address: c.bind_address, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /clash_lib/src/config/internal/convert/proxy_group.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde_yaml::Value; 4 | 5 | use crate::{Error, config::proxy::OutboundProxy}; 6 | 7 | pub fn concert( 8 | before: Option>>, 9 | proxy_names: &mut Vec, 10 | ) -> Result, crate::Error> { 11 | before.unwrap_or_default().into_iter().try_fold( 12 | HashMap::::new(), 13 | |mut rv, mapping| { 14 | let group = OutboundProxy::ProxyGroup( 15 | mapping.clone().try_into().map_err(|x| { 16 | if let Some(name) = mapping.get("name") { 17 | Error::InvalidConfig(format!("proxy group: {name:#?}: {x}")) 18 | } else { 19 | Error::InvalidConfig("proxy group name missing".to_string()) 20 | } 21 | })?, 22 | ); 23 | proxy_names.push(group.name()); 24 | rv.insert(group.name().to_string(), group); 25 | Ok::, Error>(rv) 26 | }, 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /clash_lib/src/config/internal/convert/tun.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Error, 3 | config::{config, def}, 4 | }; 5 | 6 | pub fn convert( 7 | before: Option, 8 | ) -> Result { 9 | match before { 10 | Some(t) => Ok(config::TunConfig { 11 | enable: t.enable, 12 | device_id: t.device_id, 13 | route_all: t.route_all, 14 | routes: t 15 | .routes 16 | .map(|r| { 17 | r.into_iter() 18 | .map(|x| x.parse()) 19 | .collect::, _>>() 20 | }) 21 | .transpose() 22 | .map_err(|x| { 23 | Error::InvalidConfig(format!("parse tun routes: {}", x)) 24 | })? 25 | .unwrap_or_default(), 26 | gateway: t.gateway.parse().map_err(|x| { 27 | Error::InvalidConfig(format!("parse tun gateway: {}", x)) 28 | })?, 29 | mtu: t.mtu, 30 | so_mark: t.so_mark, 31 | route_table: t.route_table, 32 | dns_hijack: match t.dns_hijack { 33 | def::DnsHijack::Switch(b) => b, 34 | def::DnsHijack::List(_) => true, 35 | }, 36 | }), 37 | None => Ok(config::TunConfig::default()), 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /clash_lib/src/config/internal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod listener; 3 | pub mod proxy; 4 | pub mod rule; 5 | 6 | pub use config::Config as InternalConfig; 7 | 8 | mod convert; 9 | -------------------------------------------------------------------------------- /clash_lib/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod def; 2 | pub mod internal; 3 | mod utils; 4 | pub use def::DNSListen; 5 | pub use internal::{InternalConfig as RuntimeConfig, *}; 6 | -------------------------------------------------------------------------------- /clash_lib/src/config/utils.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use std::{fmt::Display, str::FromStr}; 4 | 5 | pub fn deserialize_u64<'de, T, D>(deserializer: D) -> Result 6 | where 7 | D: serde::Deserializer<'de>, 8 | T: FromStr + serde::Deserialize<'de>, 9 | ::Err: Display, 10 | { 11 | #[derive(Deserialize)] 12 | #[serde(untagged)] 13 | enum StringOrNum { 14 | String(String), 15 | Num(T), 16 | } 17 | 18 | match StringOrNum::::deserialize(deserializer)? { 19 | StringOrNum::String(s) => s.parse().map_err(serde::de::Error::custom), 20 | StringOrNum::Num(n) => Ok(n), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/common.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_default_connector { 3 | ($handler:ident) => { 4 | #[async_trait] 5 | impl DialWithConnector for $handler { 6 | fn support_dialer(&self) -> Option<&str> { 7 | self.opts.common_opts.connector.as_deref() 8 | } 9 | 10 | async fn register_connector(&self, connector: Arc) { 11 | let mut m = self.connector.lock().await; 12 | *m = Some(connector); 13 | } 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/converters/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod hysteria2; 2 | #[cfg(feature = "shadowsocks")] 3 | pub mod shadowsocks; 4 | pub mod socks5; 5 | #[cfg(feature = "ssh")] 6 | pub mod ssh; 7 | #[cfg(feature = "onion")] 8 | pub mod tor; 9 | pub mod trojan; 10 | #[cfg(feature = "tuic")] 11 | pub mod tuic; 12 | pub mod vmess; 13 | pub mod wireguard; 14 | 15 | #[cfg(feature = "shadowquic")] 16 | pub mod shadowquic; 17 | mod utils; 18 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/converters/shadowquic.rs: -------------------------------------------------------------------------------- 1 | use shadowquic::config::{ 2 | default_alpn, default_congestion_control, default_initial_mtu, 3 | default_keep_alive_interval, default_min_mtu, default_over_stream, 4 | default_zero_rtt, 5 | }; 6 | 7 | use crate::{ 8 | config::proxy::OutboundShadowQuic, 9 | proxy::shadowquic::{Handler, HandlerOptions}, 10 | session::SocksAddr, 11 | }; 12 | 13 | impl TryFrom for Handler { 14 | type Error = crate::Error; 15 | 16 | fn try_from(value: OutboundShadowQuic) -> Result { 17 | (&value).try_into() 18 | } 19 | } 20 | 21 | impl TryFrom<&OutboundShadowQuic> for Handler { 22 | type Error = crate::Error; 23 | 24 | fn try_from(s: &OutboundShadowQuic) -> Result { 25 | Ok(Handler::new( 26 | s.common_opts.name.clone(), 27 | HandlerOptions { 28 | addr: SocksAddr::try_from(( 29 | s.common_opts.server.clone(), 30 | s.common_opts.port, 31 | ))? 32 | .to_string(), 33 | jls_pwd: s.jls_pwd.clone(), 34 | jls_iv: s.jls_iv.clone(), 35 | server_name: s.server_name.clone(), 36 | alpn: s.alpn.clone().unwrap_or(default_alpn()), 37 | initial_mtu: s.initial_mtu.unwrap_or(default_initial_mtu()), 38 | congestion_control: s 39 | .congestion_control 40 | .clone() 41 | .unwrap_or(default_congestion_control()), 42 | zero_rtt: s.zero_rtt.unwrap_or(default_zero_rtt()), 43 | over_stream: s.over_stream.unwrap_or(default_over_stream()), 44 | min_mtu: s.min_mtu.unwrap_or(default_min_mtu()), 45 | keep_alive_interval: s 46 | .keep_alive_interval 47 | .unwrap_or(default_keep_alive_interval()), 48 | }, 49 | )) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/converters/socks5.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config::internal::proxy::OutboundSocks5, 3 | proxy::{ 4 | HandlerCommonOptions, 5 | socks::{Handler, HandlerOptions}, 6 | transport::TlsClient, 7 | }, 8 | }; 9 | 10 | impl TryFrom for Handler { 11 | type Error = crate::Error; 12 | 13 | fn try_from(value: OutboundSocks5) -> Result { 14 | (&value).try_into() 15 | } 16 | } 17 | 18 | impl TryFrom<&OutboundSocks5> for Handler { 19 | type Error = crate::Error; 20 | 21 | fn try_from(s: &OutboundSocks5) -> Result { 22 | let tls_client = if s.tls { 23 | Some(Box::new(TlsClient::new( 24 | s.skip_cert_verify, 25 | s.sni.clone().unwrap_or(s.common_opts.server.to_owned()), 26 | None, 27 | None, 28 | )) as _) 29 | } else { 30 | None 31 | }; 32 | let h = Handler::new(HandlerOptions { 33 | name: s.common_opts.name.to_owned(), 34 | common_opts: HandlerCommonOptions { 35 | connector: s.common_opts.connect_via.clone(), 36 | ..Default::default() 37 | }, 38 | server: s.common_opts.server.to_owned(), 39 | port: s.common_opts.port, 40 | user: s.username.clone(), 41 | password: s.password.clone(), 42 | udp: s.udp, 43 | tls_client, 44 | }); 45 | Ok(h) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/converters/tor.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config::internal::proxy::OutboundTor, 3 | proxy::tor::{Handler, HandlerOptions}, 4 | }; 5 | 6 | impl TryFrom for Handler { 7 | type Error = crate::Error; 8 | 9 | fn try_from(value: OutboundTor) -> Result { 10 | (&value).try_into() 11 | } 12 | } 13 | 14 | impl TryFrom<&OutboundTor> for Handler { 15 | type Error = crate::Error; 16 | 17 | fn try_from(s: &OutboundTor) -> Result { 18 | let h = Handler::new(HandlerOptions { 19 | name: s.name.to_owned(), 20 | }); 21 | Ok(h) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/converters/utils.rs: -------------------------------------------------------------------------------- 1 | use http::uri::InvalidUri; 2 | 3 | use crate::{ 4 | config::proxy::{CommonConfigOptions, GrpcOpt, H2Opt, WsOpt}, 5 | proxy::transport::{self, GrpcClient, H2Client, WsClient}, 6 | }; 7 | 8 | impl TryFrom<(&WsOpt, &CommonConfigOptions)> for WsClient { 9 | type Error = std::io::Error; 10 | 11 | fn try_from(pair: (&WsOpt, &CommonConfigOptions)) -> Result { 12 | let (x, common) = pair; 13 | let path = x.path.as_ref().map(|x| x.to_owned()).unwrap_or_default(); 14 | let headers = x.headers.as_ref().map(|x| x.to_owned()).unwrap_or_default(); 15 | let max_early_data = x.max_early_data.unwrap_or_default() as usize; 16 | let early_data_header_name = x 17 | .early_data_header_name 18 | .as_ref() 19 | .map(|x| x.to_owned()) 20 | .unwrap_or_default(); 21 | 22 | let client = transport::WsClient::new( 23 | common.server.to_owned(), 24 | common.port, 25 | path, 26 | headers, 27 | None, 28 | max_early_data, 29 | early_data_header_name, 30 | ); 31 | Ok(client) 32 | } 33 | } 34 | 35 | impl TryFrom<(Option, &GrpcOpt, &CommonConfigOptions)> for GrpcClient { 36 | type Error = InvalidUri; 37 | 38 | fn try_from( 39 | opt: (Option, &GrpcOpt, &CommonConfigOptions), 40 | ) -> Result { 41 | let (sni, x, common) = opt; 42 | let client = transport::GrpcClient::new( 43 | sni.as_ref().unwrap_or(&common.server).to_owned(), 44 | x.grpc_service_name 45 | .as_ref() 46 | .map(|x| x.to_owned()) 47 | .unwrap_or_default() 48 | .try_into()?, 49 | ); 50 | Ok(client) 51 | } 52 | } 53 | 54 | impl TryFrom<(&H2Opt, &CommonConfigOptions)> for H2Client { 55 | type Error = InvalidUri; 56 | 57 | fn try_from(pair: (&H2Opt, &CommonConfigOptions)) -> Result { 58 | let (x, common) = pair; 59 | let host = x 60 | .host 61 | .as_ref() 62 | .map(|x| x.to_owned()) 63 | .unwrap_or(vec![common.server.to_owned()]); 64 | let path = x.path.as_ref().map(|x| x.to_owned()).unwrap_or_default(); 65 | 66 | Ok(H2Client::new( 67 | host, 68 | std::collections::HashMap::new(), 69 | http::Method::GET, 70 | path.try_into()?, 71 | )) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/group/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod fallback; 2 | pub mod loadbalance; 3 | pub mod relay; 4 | pub mod selector; 5 | pub mod smart; 6 | pub mod urltest; 7 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/http/inbound/auth.rs: -------------------------------------------------------------------------------- 1 | use base64::Engine; 2 | 3 | use http_body_util::{BodyExt, Full}; 4 | use hyper::{Request, Response}; 5 | use tracing::warn; 6 | 7 | use crate::common::{ 8 | auth::ThreadSafeAuthenticator, errors::map_io_error, http::HyperResponseBody, 9 | }; 10 | 11 | fn parse_basic_proxy_authorization( 12 | req: &Request, 13 | ) -> Option<&str> { 14 | req.headers() 15 | .get(hyper::header::PROXY_AUTHORIZATION) 16 | .map(|v| v.to_str().unwrap_or_default()) 17 | .map(|v| v.strip_prefix("Basic ")) 18 | .and_then(|v| v) 19 | } 20 | 21 | fn decode_basic_proxy_authorization(cred: &str) -> Option<(String, String)> { 22 | let decoded = base64::engine::general_purpose::STANDARD 23 | .decode(cred) 24 | .ok()?; 25 | let s = std::str::from_utf8(&decoded).ok()?; 26 | 27 | let (user, pass) = s.split_once(':')?; 28 | 29 | Some((user.to_owned(), pass.to_owned())) 30 | } 31 | 32 | /// returns a auth required response on auth failure 33 | pub fn authenticate_req( 34 | req: &Request, 35 | authenticator: ThreadSafeAuthenticator, 36 | ) -> Option> { 37 | let auth_resp = Response::builder() 38 | .status(hyper::StatusCode::PROXY_AUTHENTICATION_REQUIRED) 39 | .header(hyper::header::PROXY_AUTHENTICATE, "Basic") 40 | .body( 41 | Full::new("Proxy Auth Required".into()) 42 | .map_err(map_io_error) 43 | .boxed(), 44 | ) 45 | .unwrap(); 46 | let cred = parse_basic_proxy_authorization(req); 47 | if cred.is_none() { 48 | return Some(auth_resp); 49 | } 50 | let cred = decode_basic_proxy_authorization(cred.unwrap()); 51 | if cred.is_none() { 52 | return Some(auth_resp); 53 | } 54 | 55 | let (user, pass) = cred.unwrap(); 56 | 57 | if authenticator.authenticate(&user, &pass) { 58 | None 59 | } else { 60 | warn!("proxy authentication failed"); 61 | Some(auth_resp) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/http/inbound/connector.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Dispatcher, 3 | proxy::{AnyStream, ProxyError}, 4 | session::{Network, Session, Type}, 5 | }; 6 | use futures::FutureExt; 7 | 8 | use hyper::Uri; 9 | use std::{ 10 | future::Future, 11 | net::SocketAddr, 12 | pin::Pin, 13 | sync::Arc, 14 | task::{Context, Poll}, 15 | }; 16 | use tokio::io::duplex; 17 | 18 | use super::proxy::maybe_socks_addr; 19 | 20 | #[derive(Clone)] 21 | pub struct Connector { 22 | src: SocketAddr, 23 | dispatcher: Arc, 24 | fw_mark: Option, 25 | } 26 | 27 | impl Connector { 28 | pub fn new( 29 | src: SocketAddr, 30 | dispatcher: Arc, 31 | fw_mark: Option, 32 | ) -> Self { 33 | Self { 34 | src, 35 | dispatcher, 36 | fw_mark, 37 | } 38 | } 39 | } 40 | 41 | impl tower::Service for Connector { 42 | type Error = ProxyError; 43 | type Future = 44 | Pin> + Send>>; 45 | type Response = AnyStream; 46 | 47 | fn poll_ready( 48 | &mut self, 49 | #[allow(unused_variables)] cx: &mut Context<'_>, 50 | ) -> Poll> { 51 | Poll::Ready(Ok(())) 52 | } 53 | 54 | fn call(&mut self, url: Uri) -> Self::Future { 55 | let src = self.src; 56 | let dispatcher = self.dispatcher.clone(); 57 | 58 | let destination = maybe_socks_addr(&url); 59 | let fw_mark = self.fw_mark; 60 | async move { 61 | let (left, right) = duplex(1024 * 1024); 62 | 63 | let sess = Session { 64 | network: Network::Tcp, 65 | typ: Type::Http, 66 | source: src, 67 | destination: destination 68 | .ok_or(ProxyError::InvalidUrl(url.to_string()))?, 69 | so_mark: fw_mark, 70 | ..Default::default() 71 | }; 72 | 73 | tokio::spawn(async move { 74 | dispatcher.dispatch_stream(sess, Box::new(right)).await; 75 | }); 76 | 77 | Ok(Box::new(left) as _) 78 | } 79 | .boxed() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/http/inbound/mod.rs: -------------------------------------------------------------------------------- 1 | mod auth; 2 | mod connector; 3 | mod proxy; 4 | 5 | use crate::{ 6 | Dispatcher, 7 | common::auth::ThreadSafeAuthenticator, 8 | proxy::{inbound::InboundHandlerTrait, utils::apply_tcp_options}, 9 | }; 10 | 11 | pub use proxy::handle as handle_http; 12 | 13 | use std::{net::SocketAddr, sync::Arc}; 14 | use tokio::net::TcpListener; 15 | use tracing::warn; 16 | 17 | #[derive(Clone)] 18 | pub struct HttpInbound { 19 | addr: SocketAddr, 20 | allow_lan: bool, 21 | dispatcher: Arc, 22 | authenticator: ThreadSafeAuthenticator, 23 | fw_mark: Option, 24 | } 25 | 26 | impl Drop for HttpInbound { 27 | fn drop(&mut self) { 28 | warn!("HTTP inbound listener on {} stopped", self.addr); 29 | } 30 | } 31 | 32 | impl HttpInbound { 33 | pub fn new( 34 | addr: SocketAddr, 35 | allow_lan: bool, 36 | dispatcher: Arc, 37 | authenticator: ThreadSafeAuthenticator, 38 | fw_mark: Option, 39 | ) -> Self { 40 | Self { 41 | addr, 42 | allow_lan, 43 | dispatcher, 44 | authenticator, 45 | fw_mark, 46 | } 47 | } 48 | } 49 | 50 | impl InboundHandlerTrait for HttpInbound { 51 | fn handle_tcp(&self) -> bool { 52 | true 53 | } 54 | 55 | fn handle_udp(&self) -> bool { 56 | false 57 | } 58 | 59 | async fn listen_tcp(&self) -> anyhow::Result<()> { 60 | let listener = TcpListener::bind(self.addr).await?; 61 | 62 | loop { 63 | let (socket, _) = listener.accept().await?; 64 | let src_addr = socket.peer_addr()?; 65 | 66 | if !self.allow_lan && src_addr.ip() != socket.local_addr()?.ip() { 67 | warn!("Connection from {} is not allowed", src_addr); 68 | continue; 69 | } 70 | 71 | let socket = apply_tcp_options(socket)?; 72 | 73 | let dispatcher = self.dispatcher.clone(); 74 | let author = self.authenticator.clone(); 75 | let fw_mark = self.fw_mark; 76 | tokio::spawn(async move { 77 | proxy::handle( 78 | Box::new(socket), 79 | src_addr, 80 | dispatcher, 81 | author, 82 | fw_mark, 83 | ) 84 | .await 85 | }); 86 | } 87 | } 88 | 89 | async fn listen_udp(&self) -> anyhow::Result<()> { 90 | Err(anyhow!("unsupported")) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/http/mod.rs: -------------------------------------------------------------------------------- 1 | mod inbound; 2 | 3 | pub use inbound::{HttpInbound, handle_http}; 4 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/inbound.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::enum_dispatch; 2 | 3 | use super::{ 4 | http::HttpInbound, mixed::MixedInbound, socks::SocksInbound, 5 | tunnel::TunnelInbound, 6 | }; 7 | 8 | #[enum_dispatch(InboudHandler)] 9 | pub trait InboundHandlerTrait { 10 | /// support tcp or not 11 | fn handle_tcp(&self) -> bool; 12 | /// support udp or not 13 | fn handle_udp(&self) -> bool; 14 | async fn listen_tcp(&self) -> anyhow::Result<()>; 15 | async fn listen_udp(&self) -> anyhow::Result<()>; 16 | } 17 | 18 | #[enum_dispatch] 19 | pub enum InboudHandler { 20 | Http(HttpInbound), 21 | Socks(SocksInbound), 22 | Mixed(MixedInbound), 23 | #[cfg(target_os = "linux")] 24 | TProxy(super::tproxy::TproxyInbound), 25 | Tunnel(TunnelInbound), 26 | } 27 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/mocks.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, io}; 2 | 3 | use erased_serde::Serialize; 4 | use mockall::mock; 5 | 6 | use crate::{ 7 | app::{ 8 | dispatcher::{BoxedChainedDatagram, BoxedChainedStream}, 9 | dns::ThreadSafeDNSResolver, 10 | remote_content_manager::providers::{ 11 | Provider, ProviderType, ProviderVehicleType, 12 | proxy_provider::ProxyProvider, 13 | }, 14 | }, 15 | session::Session, 16 | }; 17 | 18 | use super::{AnyOutboundHandler, DialWithConnector, OutboundHandler, OutboundType}; 19 | 20 | mock! { 21 | pub DummyProxyProvider {} 22 | 23 | #[async_trait::async_trait] 24 | impl Provider for DummyProxyProvider { 25 | fn name(&self) -> &str; 26 | fn vehicle_type(&self) -> ProviderVehicleType; 27 | fn typ(&self) -> ProviderType; 28 | async fn initialize(&self) -> std::io::Result<()>; 29 | async fn update(&self) -> std::io::Result<()>; 30 | 31 | async fn as_map(&self) -> HashMap>; 32 | 33 | } 34 | 35 | #[async_trait::async_trait] 36 | impl ProxyProvider for DummyProxyProvider { 37 | async fn proxies(&self) -> Vec; 38 | async fn touch(&self); 39 | async fn healthcheck(&self); 40 | } 41 | } 42 | 43 | mock! { 44 | #[derive(Debug)] 45 | pub DummyOutboundHandler {} 46 | 47 | #[async_trait::async_trait] 48 | impl OutboundHandler for DummyOutboundHandler { 49 | /// The name of the outbound handler 50 | fn name(&self) -> &str; 51 | 52 | /// The protocol of the outbound handler 53 | /// only contains Type information, do not rely on the underlying value 54 | fn proto(&self) -> OutboundType; 55 | 56 | /// whether the outbound handler support UDP 57 | async fn support_udp(&self) -> bool; 58 | 59 | /// connect to remote target via TCP 60 | async fn connect_stream( 61 | &self, 62 | sess: &Session, 63 | resolver: ThreadSafeDNSResolver, 64 | ) -> io::Result; 65 | 66 | 67 | /// connect to remote target via UDP 68 | async fn connect_datagram( 69 | &self, 70 | sess: &Session, 71 | resolver: ThreadSafeDNSResolver, 72 | ) -> io::Result; 73 | 74 | /// relay related 75 | async fn support_connector(&self) -> crate::proxy::ConnectorType; 76 | } 77 | 78 | impl DialWithConnector for DummyOutboundHandler {} 79 | } 80 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/options.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default, Debug, Clone)] 2 | pub struct HandlerCommonOptions { 3 | pub connector: Option, 4 | pub icon: Option, 5 | } 6 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/reject/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::{ 3 | dispatcher::{BoxedChainedDatagram, BoxedChainedStream}, 4 | dns::ThreadSafeDNSResolver, 5 | }, 6 | config::internal::proxy::PROXY_REJECT, 7 | proxy::OutboundHandler, 8 | session::Session, 9 | }; 10 | use async_trait::async_trait; 11 | use serde::Serialize; 12 | use std::io; 13 | 14 | use super::{ConnectorType, DialWithConnector, OutboundType}; 15 | 16 | #[derive(Serialize)] 17 | pub struct Handler; 18 | 19 | impl std::fmt::Debug for Handler { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | f.debug_struct("Reject").finish() 22 | } 23 | } 24 | 25 | impl Handler { 26 | pub fn new() -> Self { 27 | Self 28 | } 29 | } 30 | 31 | impl DialWithConnector for Handler {} 32 | 33 | #[async_trait] 34 | impl OutboundHandler for Handler { 35 | fn name(&self) -> &str { 36 | PROXY_REJECT 37 | } 38 | 39 | fn proto(&self) -> OutboundType { 40 | OutboundType::Reject 41 | } 42 | 43 | async fn support_udp(&self) -> bool { 44 | false 45 | } 46 | 47 | async fn connect_stream( 48 | &self, 49 | #[allow(unused_variables)] sess: &Session, 50 | #[allow(unused_variables)] _resolver: ThreadSafeDNSResolver, 51 | ) -> io::Result { 52 | Err(io::Error::new(io::ErrorKind::Other, "REJECT")) 53 | } 54 | 55 | async fn connect_datagram( 56 | &self, 57 | #[allow(unused_variables)] sess: &Session, 58 | #[allow(unused_variables)] _resolver: ThreadSafeDNSResolver, 59 | ) -> io::Result { 60 | Err(io::Error::new(io::ErrorKind::Other, "REJECT")) 61 | } 62 | 63 | async fn support_connector(&self) -> ConnectorType { 64 | ConnectorType::All 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/shadowquic/compat.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use bytes::Bytes; 4 | use futures::{Sink, SinkExt, Stream}; 5 | use shadowquic::msgs::socks5::SocksAddr as SQAddr; 6 | use tokio::sync::mpsc::Receiver; 7 | use tokio_util::sync::PollSender; 8 | 9 | use crate::{ 10 | common::errors::new_io_error, proxy::datagram::UdpPacket, session::SocksAddr, 11 | }; 12 | 13 | use super::{to_clash_socks_addr, to_sq_socks_addr}; 14 | 15 | pub struct UdpSessionWrapper { 16 | pub s: PollSender<(Bytes, SQAddr)>, 17 | pub r: Receiver<(Bytes, SQAddr)>, 18 | pub src_addr: SocksAddr, /* source address of local socket, bound during 19 | * associate task 20 | * started */ 21 | } 22 | impl Sink for UdpSessionWrapper { 23 | type Error = io::Error; 24 | 25 | fn poll_ready( 26 | self: std::pin::Pin<&mut Self>, 27 | cx: &mut std::task::Context<'_>, 28 | ) -> std::task::Poll> { 29 | self.get_mut().s.poll_ready_unpin(cx).map_err(new_io_error) 30 | } 31 | 32 | fn start_send( 33 | self: std::pin::Pin<&mut Self>, 34 | item: UdpPacket, 35 | ) -> Result<(), Self::Error> { 36 | self.get_mut() 37 | .s 38 | .start_send_unpin((item.data.into(), to_sq_socks_addr(item.dst_addr))) 39 | .map_err(new_io_error) 40 | } 41 | 42 | fn poll_flush( 43 | self: std::pin::Pin<&mut Self>, 44 | cx: &mut std::task::Context<'_>, 45 | ) -> std::task::Poll> { 46 | self.get_mut().s.poll_flush_unpin(cx).map_err(new_io_error) 47 | } 48 | 49 | fn poll_close( 50 | self: std::pin::Pin<&mut Self>, 51 | cx: &mut std::task::Context<'_>, 52 | ) -> std::task::Poll> { 53 | self.get_mut().s.poll_close_unpin(cx).map_err(new_io_error) 54 | } 55 | } 56 | 57 | impl Stream for UdpSessionWrapper { 58 | type Item = UdpPacket; 59 | 60 | fn poll_next( 61 | mut self: std::pin::Pin<&mut Self>, 62 | cx: &mut std::task::Context<'_>, 63 | ) -> std::task::Poll> { 64 | self.r.poll_recv(cx).map(|x| { 65 | x.map(|x| UdpPacket { 66 | data: x.0.into(), 67 | src_addr: self.src_addr.clone(), 68 | dst_addr: to_clash_socks_addr(x.1), 69 | }) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/shadowsocks/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, pin::Pin}; 2 | 3 | use shadowsocks::ProxyClientStream; 4 | use tokio::io::{AsyncRead, AsyncWrite}; 5 | 6 | use crate::proxy::AnyStream; 7 | 8 | pub struct ShadowSocksStream(pub ProxyClientStream); 9 | impl Debug for ShadowSocksStream { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | f.debug_tuple("ShadowSocksStream").finish() 12 | } 13 | } 14 | 15 | impl AsyncRead for ShadowSocksStream { 16 | fn poll_read( 17 | self: std::pin::Pin<&mut Self>, 18 | cx: &mut std::task::Context<'_>, 19 | buf: &mut tokio::io::ReadBuf<'_>, 20 | ) -> std::task::Poll> { 21 | Pin::new(&mut self.get_mut().0).poll_read(cx, buf) 22 | } 23 | } 24 | 25 | impl AsyncWrite for ShadowSocksStream { 26 | fn poll_write( 27 | self: Pin<&mut Self>, 28 | cx: &mut std::task::Context<'_>, 29 | buf: &[u8], 30 | ) -> std::task::Poll> { 31 | Pin::new(&mut self.get_mut().0).poll_write(cx, buf) 32 | } 33 | 34 | fn poll_flush( 35 | self: Pin<&mut Self>, 36 | cx: &mut std::task::Context<'_>, 37 | ) -> std::task::Poll> { 38 | Pin::new(&mut self.get_mut().0).poll_flush(cx) 39 | } 40 | 41 | fn poll_shutdown( 42 | self: Pin<&mut Self>, 43 | cx: &mut std::task::Context<'_>, 44 | ) -> std::task::Poll> { 45 | Pin::new(&mut self.get_mut().0).poll_shutdown(cx) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/socks/mod.rs: -------------------------------------------------------------------------------- 1 | mod inbound; 2 | mod outbound; 3 | mod socks5; 4 | 5 | pub use inbound::{Socks5UDPCodec, SocksInbound, handle_tcp}; 6 | pub use outbound::{Handler, HandlerOptions}; 7 | pub use socks5::SOCKS5_VERSION; 8 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/ssh/connector.rs: -------------------------------------------------------------------------------- 1 | use russh::{client, keys::ssh_key}; 2 | 3 | pub struct Client { 4 | pub server_public_key: Option>, 5 | } 6 | 7 | // More SSH event handlers 8 | // can be defined in this trait 9 | // In this example, we're only using Channel, so these aren't needed. 10 | impl client::Handler for Client { 11 | type Error = russh::Error; 12 | 13 | async fn check_server_key( 14 | &mut self, 15 | server_public_key: &ssh_key::PublicKey, 16 | ) -> Result { 17 | match self.server_public_key { 18 | None => Ok(true), 19 | Some(ref key) if key.iter().any(|k| k == server_public_key) => Ok(true), 20 | _ => Err(russh::Error::UnknownKey), 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/tor/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | sync::Arc, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use arti_client::DataStream; 8 | use tokio::{ 9 | io::{AsyncRead, AsyncWrite}, 10 | sync::Mutex, 11 | }; 12 | 13 | #[derive(Debug)] 14 | pub(super) struct StreamWrapper(Arc>); 15 | 16 | impl StreamWrapper { 17 | pub(super) fn new(stream: DataStream) -> Self { 18 | Self(Arc::new(Mutex::new(stream))) 19 | } 20 | } 21 | 22 | impl AsyncRead for StreamWrapper { 23 | fn poll_read( 24 | self: Pin<&mut Self>, 25 | cx: &mut Context<'_>, 26 | buf: &mut tokio::io::ReadBuf<'_>, 27 | ) -> Poll> { 28 | match self.0.try_lock() { 29 | Ok(mut stream) => Pin::new(&mut *stream).poll_read(cx, buf), 30 | Err(_) => Poll::Pending, 31 | } 32 | } 33 | } 34 | 35 | impl AsyncWrite for StreamWrapper { 36 | fn poll_write( 37 | self: Pin<&mut Self>, 38 | cx: &mut Context<'_>, 39 | buf: &[u8], 40 | ) -> Poll> { 41 | match self.0.try_lock() { 42 | Ok(mut stream) => Pin::new(&mut *stream).poll_write(cx, buf), 43 | Err(_) => Poll::Pending, 44 | } 45 | } 46 | 47 | fn poll_flush( 48 | self: Pin<&mut Self>, 49 | cx: &mut Context<'_>, 50 | ) -> Poll> { 51 | match self.0.try_lock() { 52 | Ok(mut stream) => Pin::new(&mut *stream).poll_flush(cx), 53 | Err(_) => Poll::Pending, 54 | } 55 | } 56 | 57 | fn poll_shutdown( 58 | self: Pin<&mut Self>, 59 | cx: &mut Context<'_>, 60 | ) -> Poll> { 61 | match self.0.try_lock() { 62 | Ok(mut stream) => Pin::new(&mut *stream).poll_shutdown(cx), 63 | Err(_) => Poll::Pending, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/tproxy/iptables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ip to bypass tproxy 4 | readonly LOCAL_BY_PASS="\ 5 | 127/8 \ 6 | 10/8 \ 7 | " 8 | 9 | # declare ip as local for tproxy 10 | ip rule del fwmark 0x3333 lookup 3333 11 | ip rule add fwmark 0x3333 lookup 3333 12 | ip route del local 0.0.0.0/0 dev lo table 3333 13 | ip route add local 0.0.0.0/0 dev lo table 3333 14 | 15 | # where all traffic enter tproxy and get marked 16 | iptables -t mangle -N CLASH-TPROXY-INPUT 17 | # fill in the chain 18 | for i in $LOCAL_BY_PASS; do 19 | iptables -t mangle -A CLASH-TPROXY-INPUT -d $i -j RETURN 20 | done 21 | iptables -t mangle -A CLASH-TPROXY-INPUT -p tcp -j TPROXY \ 22 | --tproxy-mark 0x3333/0x3333 --on-port 8900 --on-ip 127.0.0.1 23 | iptables -t mangle -A CLASH-TPROXY-INPUT -p udp -j TPROXY \ 24 | --tproxy-mark 0x3333/0x3333 --on-port 8900 --on-ip 127.0.0.1 25 | 26 | # for local traffic 27 | iptables -t mangle -N CLASH-TPROXY-LOCAL 28 | for i in $LOCAL_BY_PASS; do 29 | iptables -t mangle -A CLASH-TPROXY-LOCAL -d $i -j RETURN 30 | done 31 | iptables -t mangle -A CLASH-TPROXY-LOCAL -p tcp -m conntrack --ctdir REPLY -j RETURN 32 | iptables -t mangle -A CLASH-TPROXY-LOCAL -p udp -m conntrack --ctdir REPLY -j RETURN 33 | 34 | iptables -t mangle -A CLASH-TPROXY-LOCAL -m owner --uid-owner root -j RETURN 35 | # https://github.com/shadowsocks/shadowsocks-rust/blob/6e6e6948d7fc426c99cc03ef91abae989b6482b4/configs/iptables_tproxy.sh#L187 36 | iptables -t mangle -A CLASH-TPROXY-LOCAL -p tcp -j MARK --set-xmark 0x3333/0xffffffff # needs to match the ip rule fwmark 37 | iptables -t mangle -A CLASH-TPROXY-LOCAL -p udp -j MARK --set-xmark 0x3333/0xffffffff 38 | 39 | iptables -t mangle -A OUTPUT -p tcp -d 104.21.58.154 -j CLASH-TPROXY-LOCAL 40 | iptables -t mangle -A OUTPUT -p tcp -d 172.67.161.121 -j CLASH-TPROXY-LOCAL 41 | iptables -t mangle -A OUTPUT -p udp -d 1.1.1.1 -j CLASH-TPROXY-LOCAL 42 | iptables -t mangle -A OUTPUT -p udp -d 8.8.8.8 -j CLASH-TPROXY-LOCAL 43 | 44 | # for routed traffic 45 | iptables -t mangle -A PREROUTING -p tcp -j CLASH-TPROXY-INPUT 46 | iptables -t mangle -A PREROUTING -p udp -j CLASH-TPROXY-INPUT 47 | 48 | 49 | 50 | # ipv6 51 | # TODO -------------------------------------------------------------------------------- /clash_lib/src/proxy/transport/mod.rs: -------------------------------------------------------------------------------- 1 | mod grpc; 2 | mod h2; 3 | mod shadow_tls; 4 | mod simple_obfs; 5 | mod sip003; 6 | mod tls; 7 | mod v2ray; 8 | mod ws; 9 | 10 | pub use grpc::Client as GrpcClient; 11 | pub use h2::Client as H2Client; 12 | pub use shadow_tls::Client as Shadowtls; 13 | pub use simple_obfs::*; 14 | pub use sip003::Plugin as Sip003Plugin; 15 | pub use tls::Client as TlsClient; 16 | pub use v2ray::{V2RayOBFSOption, V2rayWsClient}; 17 | pub use ws::Client as WsClient; 18 | 19 | #[async_trait::async_trait] 20 | pub trait Transport: Send + Sync { 21 | async fn proxy_stream( 22 | &self, 23 | stream: super::AnyStream, 24 | ) -> std::io::Result; 25 | } 26 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/transport/shadow_tls/prelude.rs: -------------------------------------------------------------------------------- 1 | pub(super) const TLS_MAJOR: u8 = 0x03; 2 | pub(super) const TLS_MINOR: (u8, u8) = (0x03, 0x01); 3 | pub(super) const SUPPORTED_VERSIONS_TYPE: u16 = 43; 4 | pub(super) const TLS_RANDOM_SIZE: usize = 32; 5 | pub(super) const TLS_HEADER_SIZE: usize = 5; 6 | pub(super) const TLS_SESSION_ID_SIZE: usize = 32; 7 | pub(super) const TLS_13: u16 = 0x0304; 8 | 9 | pub(super) const SERVER_HELLO: u8 = 0x02; 10 | pub(super) const HANDSHAKE: u8 = 0x16; 11 | pub(super) const APPLICATION_DATA: u8 = 0x17; 12 | 13 | pub(super) const SERVER_RANDOM_OFFSET: usize = 1 + 3 + 2; 14 | pub(super) const SESSION_ID_LEN_IDX: usize = 15 | TLS_HEADER_SIZE + 1 + 3 + 2 + TLS_RANDOM_SIZE; 16 | pub(super) const TLS_HMAC_HEADER_SIZE: usize = TLS_HEADER_SIZE + HMAC_SIZE; 17 | 18 | pub(super) const COPY_BUF_SIZE: usize = 4096; 19 | pub(super) const HMAC_SIZE: usize = 4; 20 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/transport/simple_obfs/mod.rs: -------------------------------------------------------------------------------- 1 | mod http; 2 | mod tls; 3 | 4 | #[deprecated( 5 | since = "0.1.0", 6 | note = "should be removed since v2ray-plugin is widely used" 7 | )] 8 | pub use http::Client as SimpleObfsHttp; 9 | pub use tls::Client as SimpleObfsTLS; 10 | 11 | #[derive(Clone, Copy, Debug, PartialEq)] 12 | pub enum SimpleOBFSMode { 13 | Http, 14 | Tls, 15 | } 16 | 17 | pub struct SimpleOBFSOption { 18 | pub mode: SimpleOBFSMode, 19 | pub host: String, 20 | } 21 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/transport/sip003/mod.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use super::Transport; 4 | use crate::proxy::AnyStream; 5 | 6 | #[async_trait] 7 | pub trait Plugin: Send + Sync { 8 | async fn proxy_stream(&self, stream: AnyStream) -> std::io::Result; 9 | } 10 | 11 | #[async_trait] 12 | impl Plugin for T { 13 | async fn proxy_stream(&self, stream: AnyStream) -> std::io::Result { 14 | Transport::proxy_stream(self, stream).await 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/transport/v2ray/mod.rs: -------------------------------------------------------------------------------- 1 | mod websocket; 2 | 3 | pub use websocket::V2rayWsClient; 4 | 5 | #[allow(dead_code)] 6 | pub struct V2RayOBFSOption { 7 | /// currently only websocket is supported 8 | pub mode: String, 9 | pub host: String, 10 | pub port: u16, 11 | pub path: String, 12 | pub headers: std::collections::HashMap, 13 | pub tls: bool, 14 | pub skip_cert_verify: bool, 15 | pub mux: bool, 16 | } 17 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/tuic/compat.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | task::{Context, Poll}, 4 | }; 5 | 6 | use futures::{Sink, SinkExt, Stream}; 7 | 8 | use crate::{common::errors::new_io_error, proxy::datagram::UdpPacket}; 9 | 10 | use super::TuicDatagramOutbound; 11 | 12 | impl Sink for TuicDatagramOutbound { 13 | type Error = std::io::Error; 14 | 15 | fn poll_ready( 16 | mut self: Pin<&mut Self>, 17 | cx: &mut Context<'_>, 18 | ) -> Poll> { 19 | self.send_tx 20 | .poll_ready_unpin(cx) 21 | .map_err(|v| new_io_error(format!("{v:?}"))) 22 | } 23 | 24 | fn start_send( 25 | mut self: Pin<&mut Self>, 26 | item: UdpPacket, 27 | ) -> Result<(), Self::Error> { 28 | self.send_tx 29 | .start_send_unpin(item) 30 | .map_err(|v| new_io_error(format!("{v:?}"))) 31 | } 32 | 33 | fn poll_flush( 34 | mut self: Pin<&mut Self>, 35 | cx: &mut Context<'_>, 36 | ) -> Poll> { 37 | self.send_tx 38 | .poll_flush_unpin(cx) 39 | .map_err(|v| new_io_error(format!("{v:?}"))) 40 | } 41 | 42 | fn poll_close( 43 | mut self: Pin<&mut Self>, 44 | cx: &mut Context<'_>, 45 | ) -> Poll> { 46 | self.send_tx 47 | .poll_close_unpin(cx) 48 | .map_err(|v| new_io_error(format!("{v:?}"))) 49 | } 50 | } 51 | 52 | impl Stream for TuicDatagramOutbound { 53 | type Item = UdpPacket; 54 | 55 | fn poll_next( 56 | mut self: Pin<&mut Self>, 57 | cx: &mut Context<'_>, 58 | ) -> Poll> { 59 | self.recv_rx.poll_recv(cx) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/tun/routes/other.rs: -------------------------------------------------------------------------------- 1 | use ipnet::IpNet; 2 | use tracing::warn; 3 | 4 | use crate::{app::net::OutboundInterface, config::internal::config::TunConfig}; 5 | 6 | pub fn add_route(_: &OutboundInterface, _: &IpNet) -> std::io::Result<()> { 7 | warn!("add_route is not implemented on {}", std::env::consts::OS); 8 | Ok(()) 9 | } 10 | 11 | pub fn maybe_routes_clean_up(_: &TunConfig) -> std::io::Result<()> { 12 | warn!( 13 | "maybe_routes_clean_up is not implemented on {}", 14 | std::env::consts::OS 15 | ); 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/tun/stream.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub struct StreamWrapper(netstack_lwip::TcpStream); 4 | 5 | impl StreamWrapper { 6 | pub fn new(stream: netstack_lwip::TcpStream) -> Self { 7 | Self(stream) 8 | } 9 | } 10 | impl tokio::io::AsyncRead for StreamWrapper { 11 | fn poll_read( 12 | self: std::pin::Pin<&mut Self>, 13 | cx: &mut std::task::Context<'_>, 14 | buf: &mut tokio::io::ReadBuf<'_>, 15 | ) -> std::task::Poll> { 16 | std::pin::Pin::new(&mut self.get_mut().0).poll_read(cx, buf) 17 | } 18 | } 19 | 20 | impl tokio::io::AsyncWrite for StreamWrapper { 21 | fn poll_write( 22 | self: std::pin::Pin<&mut Self>, 23 | cx: &mut std::task::Context<'_>, 24 | buf: &[u8], 25 | ) -> std::task::Poll> { 26 | std::pin::Pin::new(&mut self.get_mut().0).poll_write(cx, buf) 27 | } 28 | 29 | fn poll_flush( 30 | self: std::pin::Pin<&mut Self>, 31 | cx: &mut std::task::Context<'_>, 32 | ) -> std::task::Poll> { 33 | std::pin::Pin::new(&mut self.get_mut().0).poll_flush(cx) 34 | } 35 | 36 | fn poll_shutdown( 37 | self: std::pin::Pin<&mut Self>, 38 | cx: &mut std::task::Context<'_>, 39 | ) -> std::task::Poll> { 40 | std::pin::Pin::new(&mut self.get_mut().0).poll_shutdown(cx) 41 | } 42 | } 43 | 44 | impl From for StreamWrapper { 45 | fn from(stream: netstack_lwip::TcpStream) -> Self { 46 | Self::new(stream) 47 | } 48 | } 49 | 50 | impl std::fmt::Debug for StreamWrapper { 51 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 52 | f.debug_struct("netstack_lwip_stream").finish() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | pub mod test_utils; 3 | 4 | mod platform; 5 | 6 | pub mod provider_helper; 7 | mod proxy_connector; 8 | mod socket_helpers; 9 | 10 | pub use proxy_connector::*; 11 | pub use socket_helpers::*; 12 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/platform/apple.rs: -------------------------------------------------------------------------------- 1 | use std::{io, net::SocketAddr}; 2 | 3 | use tracing::warn; 4 | 5 | use crate::{app::net::Interface, common::errors::new_io_error}; 6 | 7 | pub(crate) fn must_bind_socket_on_interface( 8 | socket: &socket2::Socket, 9 | iface: &Interface, 10 | family: socket2::Domain, 11 | ) -> io::Result<()> { 12 | match iface { 13 | Interface::IpAddr(ip) => socket.bind(&SocketAddr::new(*ip, 0).into()), 14 | Interface::Name(name) => { 15 | let index = 16 | unsafe { libc::if_nametoindex(name.as_str().as_ptr() as *const _) }; 17 | if index == 0 { 18 | warn!("failed to get interface index: {}", name); 19 | return Err(new_io_error(format!( 20 | "failed to get interface index: {}", 21 | name 22 | ))); 23 | } 24 | match family { 25 | socket2::Domain::IPV4 => { 26 | socket.bind_device_by_index_v4(std::num::NonZeroU32::new(index)) 27 | } 28 | socket2::Domain::IPV6 => { 29 | socket.bind_device_by_index_v6(std::num::NonZeroU32::new(index)) 30 | } 31 | _ => Err(io::Error::new( 32 | io::ErrorKind::InvalidInput, 33 | "unsupported socket family", 34 | )), 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/platform/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_vendor = "apple")] 2 | mod apple; 3 | #[cfg(target_vendor = "apple")] 4 | pub(crate) use apple::must_bind_socket_on_interface; 5 | #[cfg(any(target_os = "fuchsia", target_os = "linux", target_os = "freebsd"))] 6 | pub(crate) mod unix; 7 | #[cfg(any(target_os = "fuchsia", target_os = "linux", target_os = "freebsd"))] 8 | pub(crate) use unix::must_bind_socket_on_interface; 9 | #[cfg(windows)] 10 | pub(crate) mod win; 11 | #[cfg(windows)] 12 | pub(crate) use win::must_bind_socket_on_interface; 13 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/platform/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{io, net::SocketAddr}; 2 | 3 | use crate::app::net::Interface; 4 | 5 | #[allow(dead_code)] 6 | pub(crate) fn must_bind_socket_on_interface( 7 | socket: &socket2::Socket, 8 | iface: &Interface, 9 | _family: socket2::Domain, 10 | ) -> io::Result<()> { 11 | match iface { 12 | Interface::IpAddr(ip) => socket.bind(&SocketAddr::new(*ip, 0).into()), 13 | Interface::Name(name) => { 14 | #[cfg(any( 15 | target_os = "android", 16 | target_os = "fuchsia", 17 | target_os = "linux", 18 | ))] 19 | { 20 | socket.bind_device(Some(name.as_bytes())) 21 | } 22 | #[cfg(not(any( 23 | target_os = "android", 24 | target_os = "fuchsia", 25 | target_os = "linux", 26 | )))] 27 | { 28 | use crate::common::errors::new_io_error; 29 | Err(new_io_error(format!("unsupported platform: {}", name))) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/provider_helper.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::remote_content_manager::providers::proxy_provider::ThreadSafeProxyProvider, 3 | proxy::AnyOutboundHandler, 4 | }; 5 | 6 | pub async fn get_proxies_from_providers( 7 | providers: &Vec, 8 | touch: bool, 9 | ) -> Vec { 10 | let mut proxies = vec![]; 11 | for provider in providers { 12 | if touch { 13 | provider.read().await.touch().await; 14 | } 15 | 16 | let mut proxies_from_provider = 17 | provider.read().await.proxies().await.to_vec(); 18 | proxies.append(&mut proxies_from_provider); 19 | } 20 | proxies 21 | } 22 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/test_utils/docker_utils/config_helper.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use std::{path::PathBuf, sync::Arc}; 3 | use tracing::debug; 4 | 5 | use crate::{ 6 | Config, 7 | app::{ 8 | dns::{self, ClashResolver, SystemResolver}, 9 | profile, 10 | }, 11 | common::{http::new_http_client, mmdb}, 12 | }; 13 | 14 | pub fn root_dir() -> PathBuf { 15 | let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR").to_owned()); 16 | // remove the clash_lib 17 | root.pop(); 18 | root 19 | } 20 | 21 | pub fn test_config_base_dir() -> PathBuf { 22 | root_dir().join("clash/tests/data/config") 23 | } 24 | 25 | // load the config from test dir 26 | // and return the dns resolver for the proxy 27 | pub async fn build_dns_resolver() -> anyhow::Result> { 28 | let root = root_dir(); 29 | let test_base_dir = test_config_base_dir(); 30 | let config_path = test_base_dir 31 | .join("empty.yaml") 32 | .to_str() 33 | .unwrap() 34 | .to_owned(); 35 | let config = Config::File(config_path).try_parse()?; 36 | let mmdb_path = test_base_dir.join("Country.mmdb"); 37 | let system_resolver = Arc::new( 38 | SystemResolver::new(false).map_err(|x| Error::DNSError(x.to_string()))?, 39 | ); 40 | let client = new_http_client(system_resolver) 41 | .map_err(|x| Error::DNSError(x.to_string()))?; 42 | 43 | let mmdb = Arc::new( 44 | mmdb::Mmdb::new(mmdb_path, config.general.mmdb_download_url.clone(), client) 45 | .await?, 46 | ); 47 | 48 | debug!("initializing cache store"); 49 | let cache_store = profile::ThreadSafeCacheFile::new( 50 | root.join("cache.db").as_path().to_str().unwrap(), 51 | config.profile.store_selected, 52 | ); 53 | 54 | let dns_resolver = Arc::new( 55 | dns::EnhancedResolver::new(config.dns, cache_store.clone(), mmdb.clone()) 56 | .await, 57 | ); 58 | 59 | Ok(dns_resolver) 60 | } 61 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/test_utils/docker_utils/consts.rs: -------------------------------------------------------------------------------- 1 | pub const LOCAL_ADDR: &str = "127.0.0.1"; 2 | 3 | pub const IMAGE_WG: &str = "lscr.io/linuxserver/wireguard:1.0.20210914-legacy"; 4 | // image with v2ray-plugin pre-installed 5 | #[cfg(feature = "shadowsocks")] 6 | pub const IMAGE_SS_RUST: &str = "teddysun/shadowsocks-rust:alpine-1.22.0"; 7 | #[cfg(feature = "shadowsocks")] 8 | pub const IMAGE_SHADOW_TLS: &str = "ghcr.io/ihciah/shadow-tls:latest"; 9 | #[cfg(feature = "shadowsocks")] 10 | pub const IMAGE_OBFS: &str = "gists/simple-obfs:latest"; 11 | pub const IMAGE_TROJAN_GO: &str = "p4gefau1t/trojan-go:latest"; 12 | pub const IMAGE_VMESS: &str = "v2fly/v2fly-core:v4.45.2"; 13 | pub const IMAGE_XRAY: &str = "teddysun/xray:latest"; 14 | pub const IMAGE_SOCKS5: &str = "ghcr.io/wzshiming/socks5/socks5:v0.4.3"; 15 | #[cfg(feature = "ssh")] 16 | pub const IMAGE_OPENSSH: &str = "docker.io/linuxserver/openssh-server:latest"; 17 | pub const IMAGE_HYSTERIA: &str = "tobyxdd/hysteria:latest"; 18 | #[cfg(feature = "tuic")] 19 | pub const IMAGE_TUIC: &str = "ghcr.io/itsusinn/tuic-server:latest"; 20 | #[cfg(feature = "shadowquic")] 21 | pub const IMAGE_SHADOWQUIC: &str = "ghcr.io/spongebob888/shadowquic:latest"; 22 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/utils/test_utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod noop; 2 | 3 | #[cfg(docker_test)] 4 | pub mod docker_utils; 5 | #[cfg(docker_test)] 6 | pub use docker_utils::*; 7 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/vmess/vmess_impl/client.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use crate::{common::utils, proxy::AnyStream, session::SocksAddr}; 4 | 5 | use super::{ 6 | SECURITY_AES_128_GCM, SECURITY_CHACHA20_POLY1305, SECURITY_NONE, Security, 7 | stream::{self}, 8 | user::{self, new_alter_id_list}, 9 | }; 10 | 11 | #[derive(Clone)] 12 | pub struct VmessOption { 13 | pub uuid: String, 14 | pub alter_id: u16, 15 | pub security: String, 16 | pub udp: bool, 17 | pub dst: SocksAddr, 18 | } 19 | 20 | pub struct Builder { 21 | pub user: Vec, 22 | pub security: Security, 23 | pub is_aead: bool, 24 | pub is_udp: bool, 25 | pub dst: SocksAddr, 26 | } 27 | 28 | impl Builder { 29 | pub fn new(opt: &VmessOption) -> io::Result { 30 | let uuid = uuid::Uuid::parse_str(&opt.uuid).map_err(|_| { 31 | io::Error::new( 32 | io::ErrorKind::InvalidInput, 33 | "invalid uuid format, should be \ 34 | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 35 | ) 36 | })?; 37 | 38 | let security = match opt.security.to_lowercase().as_str() { 39 | "chacha20-poly1305" => SECURITY_CHACHA20_POLY1305, 40 | "aes-128-gcm" => SECURITY_AES_128_GCM, 41 | "none" => SECURITY_NONE, 42 | "auto" => match std::env::consts::ARCH { 43 | "x86_64" | "s390x" | "aarch64" => SECURITY_AES_128_GCM, 44 | _ => SECURITY_CHACHA20_POLY1305, 45 | }, 46 | _ => { 47 | return Err(io::Error::new( 48 | io::ErrorKind::InvalidInput, 49 | "invalid security", 50 | )); 51 | } 52 | }; 53 | 54 | Ok(Self { 55 | user: new_alter_id_list(&user::new_id(&uuid), opt.alter_id), 56 | security, 57 | is_aead: opt.alter_id == 0, 58 | is_udp: opt.udp, 59 | dst: opt.dst.clone(), 60 | }) 61 | } 62 | 63 | pub async fn proxy_stream(&self, stream: AnyStream) -> io::Result { 64 | let idx = utils::rand_range(0..self.user.len()); 65 | let stream = stream::VmessStream::new( 66 | stream, 67 | &self.user[idx], 68 | &self.dst, 69 | &self.security, 70 | self.is_aead, 71 | self.is_udp, 72 | ) 73 | .await?; 74 | 75 | Ok(Box::new(stream)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/vmess/vmess_impl/mod.rs: -------------------------------------------------------------------------------- 1 | mod cipher; 2 | mod client; 3 | mod header; 4 | // pub mod http; 5 | mod datagram; 6 | mod kdf; 7 | mod stream; 8 | mod user; 9 | 10 | pub(crate) const VERSION: u8 = 1; 11 | 12 | pub(crate) const OPTION_CHUNK_STREAM: u8 = 1; 13 | #[allow(unused)] 14 | pub(crate) const OPTION_CHUNK_MASK: u8 = 2; 15 | 16 | type Security = u8; 17 | 18 | pub(crate) const SECURITY_AES_128_GCM: Security = 3; 19 | pub(crate) const SECURITY_CHACHA20_POLY1305: Security = 4; 20 | pub(crate) const SECURITY_NONE: Security = 5; 21 | 22 | pub(crate) const COMMAND_TCP: u8 = 1; 23 | pub(crate) const COMMAND_UDP: u8 = 2; 24 | 25 | const CHUNK_SIZE: usize = 1 << 14; 26 | const MAX_CHUNK_SIZE: usize = 17 * 1024; 27 | 28 | pub use client::{Builder, VmessOption}; 29 | pub use datagram::OutboundDatagramVmess; 30 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/vmess/vmess_impl/user.rs: -------------------------------------------------------------------------------- 1 | use md5::Digest; 2 | 3 | #[derive(Clone)] 4 | pub struct ID { 5 | pub uuid: uuid::Uuid, 6 | pub cmd_key: [u8; 16], 7 | } 8 | 9 | pub fn new_alter_id_list(primary: &ID, alter_id_count: u16) -> Vec { 10 | let mut alter_id_list = Vec::with_capacity(alter_id_count as usize); 11 | let mut prev_id = primary.uuid; 12 | 13 | for _ in 0..alter_id_count { 14 | let new_id = next_id(&prev_id); 15 | alter_id_list.push(ID { 16 | uuid: new_id, 17 | cmd_key: primary.cmd_key, 18 | }); 19 | prev_id = new_id; 20 | } 21 | 22 | alter_id_list.push(primary.to_owned()); 23 | alter_id_list 24 | } 25 | 26 | /// TODO docs 27 | pub fn new_id(uuid: &uuid::Uuid) -> ID { 28 | let uuid = uuid.to_owned(); 29 | let mut hasher = md5::Md5::new(); 30 | hasher.update(uuid.as_bytes()); 31 | hasher.update(b"c48619fe-8f02-49e0-b9e9-edf763e17e21"); // What? 32 | let cmd_key: [u8; 16] = hasher.finalize().into(); 33 | ID { uuid, cmd_key } 34 | } 35 | 36 | /// TODO docs 37 | fn next_id(i: &uuid::Uuid) -> uuid::Uuid { 38 | let mut hasher = md5::Md5::new(); 39 | hasher.update(i.as_bytes()); 40 | hasher.update(b"16167dc8-16b6-4e6d-b8bb-65dd68113a81"); // Why? 41 | let buf: [u8; 16] = hasher.finalize().into(); 42 | uuid::Uuid::from_bytes(buf) 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | #[test] 48 | fn test_new_id() { 49 | let id = super::new_id( 50 | &uuid::Uuid::parse_str("b831381d-6324-4d53-ad4f-8cda48b30811").unwrap(), 51 | ); 52 | assert_eq!(id.uuid.to_string(), "b831381d-6324-4d53-ad4f-8cda48b30811"); 53 | assert_eq!( 54 | id.cmd_key, 55 | [ 56 | 181, 13, 145, 106, 192, 206, 192, 103, 152, 26, 248, 229, 243, 138, 57 | 117, 143 58 | ] 59 | ); 60 | } 61 | 62 | #[test] 63 | fn test_next_id() { 64 | let id = super::new_id( 65 | &uuid::Uuid::parse_str("b831381d-6324-4d53-ad4f-8cda48b30811").unwrap(), 66 | ); 67 | let next_id = super::next_id(&id.uuid); 68 | assert_eq!(next_id.to_string(), "5a071834-12d5-980a-72ac-845d5568d17d"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/wg/events.rs: -------------------------------------------------------------------------------- 1 | /// Layer 7 protocols for ports. 2 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] 3 | pub enum PortProtocol { 4 | /// TCP 5 | Tcp, 6 | /// UDP 7 | Udp, 8 | } 9 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/wg/keys.rs: -------------------------------------------------------------------------------- 1 | use base64::{Engine, engine::general_purpose::STANDARD}; 2 | 3 | pub(crate) struct KeyBytes(pub [u8; 32]); 4 | 5 | impl std::str::FromStr for KeyBytes { 6 | type Err = &'static str; 7 | 8 | /// Can parse a secret key from a hex or base64 encoded string. 9 | fn from_str(s: &str) -> Result { 10 | let mut internal = [0u8; 32]; 11 | 12 | match s.len() { 13 | 64 => { 14 | // Try to parse as hex 15 | for i in 0..32 { 16 | internal[i] = u8::from_str_radix(&s[i * 2..=i * 2 + 1], 16) 17 | .map_err(|_| "Illegal character in key")?; 18 | } 19 | } 20 | 43 | 44 => { 21 | // Try to parse as base64 22 | if let Ok(decoded_key) = STANDARD.decode(s) { 23 | if decoded_key.len() == internal.len() { 24 | internal[..].copy_from_slice(&decoded_key); 25 | } else { 26 | return Err("Illegal character in key"); 27 | } 28 | } 29 | } 30 | _ => return Err("Illegal key size"), 31 | } 32 | 33 | Ok(KeyBytes(internal)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/wg/ports.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::VecDeque, ops::Range, sync::Arc}; 2 | 3 | use anyhow::Context; 4 | use rand::seq::SliceRandom; 5 | 6 | const MIN_PORT: u16 = 1025; 7 | const MAX_PORT: u16 = 60000; 8 | const PORT_RANGE: Range = MIN_PORT..MAX_PORT; 9 | 10 | /// A pool of virtual ports available for TCP connections. 11 | #[derive(Clone)] 12 | pub struct PortPool { 13 | inner: Arc>, 14 | } 15 | 16 | impl Default for PortPool { 17 | fn default() -> Self { 18 | Self::new() 19 | } 20 | } 21 | 22 | impl PortPool { 23 | /// Initializes a new pool of virtual ports. 24 | pub fn new() -> Self { 25 | let mut inner = TcpPortPoolInner::default(); 26 | let mut ports: Vec = PORT_RANGE.collect(); 27 | ports.shuffle(&mut rand::rng()); 28 | ports 29 | .into_iter() 30 | .for_each(|p| inner.queue.push_back(p) as ()); 31 | Self { 32 | inner: Arc::new(tokio::sync::RwLock::new(inner)), 33 | } 34 | } 35 | 36 | /// Requests a free port from the pool. An error is returned if none is 37 | /// available (exhausted max capacity). 38 | pub async fn next(&self) -> anyhow::Result { 39 | let mut inner = self.inner.write().await; 40 | let port = inner 41 | .queue 42 | .pop_front() 43 | .with_context(|| "virtual port pool is exhausted")?; 44 | Ok(port) 45 | } 46 | 47 | /// Releases a port back into the pool. 48 | pub async fn release(&self, port: u16) { 49 | let mut inner = self.inner.write().await; 50 | inner.queue.push_back(port); 51 | } 52 | } 53 | 54 | /// Non thread-safe inner logic for TCP port pool. 55 | #[derive(Debug, Default)] 56 | struct TcpPortPoolInner { 57 | /// Remaining ports in the pool. 58 | queue: VecDeque, 59 | } 60 | -------------------------------------------------------------------------------- /clash_lib/src/proxy/wg/stack/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod tcp; 2 | pub mod udp; 3 | -------------------------------------------------------------------------------- /clash_lib/tests/data/Country.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/clash_lib/tests/data/Country.mmdb -------------------------------------------------------------------------------- /docs/.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/.lock -------------------------------------------------------------------------------- /docs/clash_doc/sidebar-items.js: -------------------------------------------------------------------------------- 1 | window.SIDEBAR_ITEMS = {"struct":["ClashConfigDef","ClashDNSConfigDef"]}; -------------------------------------------------------------------------------- /docs/crates.js: -------------------------------------------------------------------------------- 1 | window.ALL_CRATES = ["clash_doc"]; -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/source-files.js: -------------------------------------------------------------------------------- 1 | var sourcesIndex = JSON.parse('{\ 2 | "clash_doc":["",[],["lib.rs"]]\ 3 | }'); 4 | createSourceSidebar(); 5 | -------------------------------------------------------------------------------- /docs/static.files/COPYRIGHT-23e9bde6c69aea69.txt: -------------------------------------------------------------------------------- 1 | # REUSE-IgnoreStart 2 | 3 | These documentation pages include resources by third parties. This copyright 4 | file applies only to those resources. The following third party resources are 5 | included, and carry their own copyright notices and license terms: 6 | 7 | * Fira Sans (FiraSans-Regular.woff2, FiraSans-Medium.woff2): 8 | 9 | Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ 10 | with Reserved Font Name Fira Sans. 11 | 12 | Copyright (c) 2014, Telefonica S.A. 13 | 14 | Licensed under the SIL Open Font License, Version 1.1. 15 | See FiraSans-LICENSE.txt. 16 | 17 | * rustdoc.css, main.js, and playpen.js: 18 | 19 | Copyright 2015 The Rust Developers. 20 | Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE.txt) or 21 | the MIT license (LICENSE-MIT.txt) at your option. 22 | 23 | * normalize.css: 24 | 25 | Copyright (c) Nicolas Gallagher and Jonathan Neal. 26 | Licensed under the MIT license (see LICENSE-MIT.txt). 27 | 28 | * Source Code Pro (SourceCodePro-Regular.ttf.woff2, 29 | SourceCodePro-Semibold.ttf.woff2, SourceCodePro-It.ttf.woff2): 30 | 31 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), 32 | with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark 33 | of Adobe Systems Incorporated in the United States and/or other countries. 34 | 35 | Licensed under the SIL Open Font License, Version 1.1. 36 | See SourceCodePro-LICENSE.txt. 37 | 38 | * Source Serif 4 (SourceSerif4-Regular.ttf.woff2, SourceSerif4-Bold.ttf.woff2, 39 | SourceSerif4-It.ttf.woff2): 40 | 41 | Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 42 | 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United 43 | States and/or other countries. 44 | 45 | Licensed under the SIL Open Font License, Version 1.1. 46 | See SourceSerif4-LICENSE.md. 47 | 48 | This copyright file is intended to be distributed with rustdoc output. 49 | 50 | # REUSE-IgnoreEnd 51 | -------------------------------------------------------------------------------- /docs/static.files/FiraSans-Medium-8f9a781e4970d388.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/FiraSans-Medium-8f9a781e4970d388.woff2 -------------------------------------------------------------------------------- /docs/static.files/FiraSans-Regular-018c141bf0843ffd.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/FiraSans-Regular-018c141bf0843ffd.woff2 -------------------------------------------------------------------------------- /docs/static.files/LICENSE-MIT-65090b722b3f6c56.txt: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /docs/static.files/NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/SourceSerif4-Regular-46f98efaafac5295.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/SourceSerif4-Regular-46f98efaafac5295.ttf.woff2 -------------------------------------------------------------------------------- /docs/static.files/clipboard-7571035ce49a181d.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static.files/favicon-16x16-8b506e7a72182f1c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/favicon-16x16-8b506e7a72182f1c.png -------------------------------------------------------------------------------- /docs/static.files/favicon-32x32-422f7d1d52889060.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Watfaq/clash-rs/21f880729f0bbcd4c3f4fda5a8b64b2f0253ad58/docs/static.files/favicon-32x32-422f7d1d52889060.png -------------------------------------------------------------------------------- /docs/static.files/normalize-76eba96aa4d2e634.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} -------------------------------------------------------------------------------- /docs/static.files/noscript-13285aec31fa243e.css: -------------------------------------------------------------------------------- 1 | #main-content .attributes{margin-left:0 !important;}#copy-path{display:none;}nav.sub{display:none;}.source .sidebar{display:none;}.notable-traits{display:none;} -------------------------------------------------------------------------------- /docs/static.files/scrape-examples-ef1e698c1d417c0c.js: -------------------------------------------------------------------------------- 1 | "use strict";(function(){const DEFAULT_MAX_LINES=5;const HIDDEN_MAX_LINES=10;function scrollToLoc(elt,loc,isHidden){const lines=elt.querySelector(".src-line-numbers");let scrollOffset;const maxLines=isHidden?HIDDEN_MAX_LINES:DEFAULT_MAX_LINES;if(loc[1]-loc[0]>maxLines){const line=Math.max(0,loc[0]-1);scrollOffset=lines.children[line].offsetTop}else{const wrapper=elt.querySelector(".code-wrapper");const halfHeight=wrapper.offsetHeight/2;const offsetTop=lines.children[loc[0]].offsetTop;const lastLine=lines.children[loc[1]];const offsetBot=lastLine.offsetTop+lastLine.offsetHeight;const offsetMid=(offsetTop+offsetBot)/2;scrollOffset=offsetMid-halfHeight}lines.scrollTo(0,scrollOffset);elt.querySelector(".rust").scrollTo(0,scrollOffset)}function updateScrapedExample(example,isHidden){const locs=JSON.parse(example.attributes.getNamedItem("data-locs").textContent);let locIndex=0;const highlights=Array.prototype.slice.call(example.querySelectorAll(".highlight"));const link=example.querySelector(".scraped-example-title a");if(locs.length>1){const onChangeLoc=changeIndex=>{removeClass(highlights[locIndex],"focus");changeIndex();scrollToLoc(example,locs[locIndex][0],isHidden);addClass(highlights[locIndex],"focus");const url=locs[locIndex][1];const title=locs[locIndex][2];link.href=url;link.innerHTML=title};example.querySelector(".prev").addEventListener("click",()=>{onChangeLoc(()=>{locIndex=(locIndex-1+locs.length)%locs.length})});example.querySelector(".next").addEventListener("click",()=>{onChangeLoc(()=>{locIndex=(locIndex+1)%locs.length})})}const expandButton=example.querySelector(".expand");if(expandButton){expandButton.addEventListener("click",()=>{if(hasClass(example,"expanded")){removeClass(example,"expanded");scrollToLoc(example,locs[0][0],isHidden)}else{addClass(example,"expanded")}})}scrollToLoc(example,locs[0][0],isHidden)}const firstExamples=document.querySelectorAll(".scraped-example-list > .scraped-example");onEachLazy(firstExamples,el=>updateScrapedExample(el,false));onEachLazy(document.querySelectorAll(".more-examples-toggle"),toggle=>{onEachLazy(toggle.querySelectorAll(".toggle-line, .hide-more"),button=>{button.addEventListener("click",()=>{toggle.open=false})});const moreExamples=toggle.querySelectorAll(".scraped-example");toggle.querySelector("summary").addEventListener("click",()=>{setTimeout(()=>{onEachLazy(moreExamples,el=>updateScrapedExample(el,true))})},{once:true})})})() -------------------------------------------------------------------------------- /docs/static.files/settings-7bfb4c59cc6bc502.css: -------------------------------------------------------------------------------- 1 | .setting-line{margin:1.2em 0.6em;position:relative;}.setting-radio input,.setting-check input{margin-right:0.3em;height:1.2rem;width:1.2rem;color:inherit;border:2px solid var(--settings-input-border-color);outline:none;-webkit-appearance:none;cursor:pointer;}.setting-radio input{border-radius:50%;}.setting-check input:checked{content:url('data:image/svg+xml,\ 2 | \ 3 | ');}.setting-radio span,.setting-check span{padding-bottom:1px;}.setting-radio{margin-top:0.1em;margin-bottom:0.1em;min-width:3.8em;padding:0.3em;display:inline-flex;align-items:center;cursor:pointer;}.setting-radio+.setting-radio{margin-left:0.5em;}.setting-check{margin-right:20px;display:flex;align-items:center;cursor:pointer;}.setting-radio input:checked{box-shadow:inset 0 0 0 3px var(--main-background-color);background-color:var(--settings-input-color);}.setting-check input:checked{background-color:var(--settings-input-color);border-width:1px;}.setting-radio input:focus,.setting-check input:focus{box-shadow:0 0 1px 1px var(--settings-input-color);}.setting-radio input:checked:focus{box-shadow:inset 0 0 0 3px var(--main-background-color),0 0 2px 2px var(--settings-input-color);}.setting-radio input:hover,.setting-check input:hover{border-color:var(--settings-input-color) !important;} -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-03-01" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | reorder_modules = true 3 | reorder_impl_items = true 4 | reorder_imports = true 5 | format_strings = true 6 | format_code_in_doc_comments = true 7 | format_macro_matchers = true 8 | condense_wildcard_suffixes = true 9 | normalize_comments = true 10 | use_try_shorthand = true 11 | wrap_comments = true 12 | use_field_init_shorthand = true 13 | error_on_line_overflow = false 14 | tab_spaces = 4 15 | enum_discrim_align_threshold = 20 16 | max_width = 85 17 | imports_granularity = "Crate" 18 | -------------------------------------------------------------------------------- /scripts/logs.py: -------------------------------------------------------------------------------- 1 | from websockets.sync.client import connect 2 | 3 | 4 | def hello(): 5 | with connect("ws://127.1:9090/logs", additional_headers={"Authorization": "Bearer clash-rs"}) as websocket: 6 | while True: 7 | message = websocket.recv() 8 | print(f"Received: {message}") 9 | 10 | 11 | hello() 12 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | PySocks 2 | websockets --------------------------------------------------------------------------------