├── .cargo └── config.toml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github └── workflows │ ├── docs.yml │ ├── rust.yml │ └── update_schema.yml ├── .gitignore ├── .gitmodules ├── .rusty-hook.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benchmark ├── README.md ├── bench.sh ├── benchmark.yaml └── results │ ├── iperf3-aes-128-gcm.txt │ ├── iperf3-aes-256-gcm.txt │ ├── iperf3-chacha20-ietf-poly1305.txt │ ├── iperf3-forward.txt │ ├── iperf3-rc4-md5.txt │ └── iperf3.txt ├── cgr ├── cgr.bat ├── coverage └── .gitignore ├── docs ├── .gitignore ├── .vscode │ └── settings.json ├── README.md ├── babel.config.js ├── docs │ ├── config │ │ ├── basic.md │ │ └── format.md │ ├── inside │ │ └── README.md │ ├── tutorial-basics │ │ └── _category_.json │ └── tutorial │ │ ├── installation.md │ │ ├── introduction.md │ │ └── usage.md ├── docusaurus.config.ts ├── i18n │ └── en │ │ └── intro.md ├── package.json ├── pnpm-lock.yaml ├── sidebars.ts ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ └── img │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── features │ │ ├── api.svg │ │ ├── clash.svg │ │ ├── config.svg │ │ ├── cross-platform.svg │ │ ├── plugin.svg │ │ ├── protocol.svg │ │ ├── reload.svg │ │ ├── rule.svg │ │ └── schema.svg │ │ ├── logo.svg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg └── tsconfig.json ├── docs_old └── telemetry.md ├── examples ├── gateway.yaml ├── geoip.yaml ├── http_auth_server.yaml ├── nft-tproxy │ ├── reset.sh │ └── setup.sh ├── raw.yaml ├── socks5_server.yaml ├── tproxy │ ├── reset.sh │ ├── setup.sh │ └── tproxy.yaml └── tracing │ ├── config │ ├── grafana-datasources.yaml │ ├── grafana.ini │ ├── overrides.yaml │ ├── prometheus.yaml │ ├── tempo.yaml │ └── vector.toml │ └── docker-compose.yml ├── ffi ├── Cargo.toml ├── build.rs ├── rdp.h └── src │ └── lib.rs ├── gen_coverage.sh ├── protocol ├── obfs │ ├── Cargo.toml │ └── src │ │ ├── http_simple.rs │ │ ├── lib.rs │ │ ├── obfs_net.rs │ │ └── plain.rs ├── raw │ ├── Cargo.toml │ └── src │ │ ├── config.rs │ │ ├── device.rs │ │ ├── device │ │ ├── boxed.rs │ │ ├── interface_info.rs │ │ ├── pcap_dev.rs │ │ ├── unix.rs │ │ └── windows.rs │ │ ├── forward.rs │ │ ├── forward │ │ └── source.rs │ │ ├── gateway.rs │ │ ├── lib.rs │ │ ├── net.rs │ │ ├── server.rs │ │ └── wrap.rs ├── rpc │ ├── Cargo.toml │ └── src │ │ ├── connection.rs │ │ ├── lib.rs │ │ ├── net.rs │ │ ├── net │ │ └── socket.rs │ │ ├── server.rs │ │ ├── session.rs │ │ ├── session │ │ └── state.rs │ │ ├── tests.rs │ │ └── types.rs ├── ss │ ├── Cargo.toml │ └── src │ │ ├── client.rs │ │ ├── lib.rs │ │ ├── server.rs │ │ ├── server │ │ └── source.rs │ │ ├── tests.rs │ │ ├── udp.rs │ │ └── wrapper.rs └── trojan │ ├── Cargo.toml │ └── src │ ├── client.rs │ ├── client │ ├── tcp.rs │ └── udp.rs │ ├── lib.rs │ ├── stream.rs │ └── websocket.rs ├── rabbit-digger ├── Cargo.toml ├── README.md └── src │ ├── builtin.rs │ ├── config.rs │ ├── config │ └── default.rs │ ├── lib.rs │ ├── rabbit_digger.rs │ ├── rabbit_digger │ ├── connection_manager.rs │ ├── event.rs │ └── running.rs │ ├── registry.rs │ └── util.rs ├── rd-derive ├── Cargo.toml └── src │ └── lib.rs ├── rd-interface ├── Cargo.toml ├── README.md └── src │ ├── address.rs │ ├── config.rs │ ├── config │ ├── compact_vec_string.rs │ ├── resolvable.rs │ └── single_or_vec.rs │ ├── constant.rs │ ├── context.rs │ ├── error.rs │ ├── interface.rs │ ├── lib.rs │ ├── macros.rs │ └── registry.rs ├── rd-std ├── Cargo.toml └── src │ ├── builtin.rs │ ├── builtin │ ├── alias.rs │ ├── blackhole.rs │ ├── combine.rs │ ├── dns.rs │ ├── echo.rs │ ├── forward.rs │ ├── local.rs │ ├── noop.rs │ └── resolve.rs │ ├── context.rs │ ├── context │ ├── connect_tcp.rs │ └── connect_udp.rs │ ├── http.rs │ ├── http │ ├── client.rs │ ├── server.rs │ └── tests.rs │ ├── lib.rs │ ├── mixed.rs │ ├── rule.rs │ ├── rule │ ├── any.rs │ ├── config.rs │ ├── domain.rs │ ├── geoip.rs │ ├── ipcidr.rs │ ├── matcher.rs │ └── rule_net.rs │ ├── sniffer.rs │ ├── sniffer │ ├── dns_sniffer.rs │ ├── service.rs │ └── sni_sniffer.rs │ ├── socks5.rs │ ├── socks5 │ ├── client.rs │ ├── common.rs │ ├── server.rs │ └── tests.rs │ ├── tests.rs │ ├── tests │ ├── channel.rs │ └── net.rs │ ├── tls.rs │ ├── tls │ ├── native-tls.rs │ ├── openssl.rs │ └── rustls.rs │ ├── transparent.rs │ ├── transparent │ ├── origin_addr.rs │ ├── redir.rs │ ├── socket.rs │ └── tproxy.rs │ ├── util.rs │ └── util │ ├── async_fn.rs │ ├── connect_tcp │ └── unix.rs │ ├── drop_abort.rs │ ├── forward_udp.rs │ ├── forward_udp │ ├── connection.rs │ └── send_back.rs │ ├── lru_cache.rs │ ├── net.rs │ ├── peekable_tcpstream.rs │ ├── poll_future.rs │ └── udp_connector.rs ├── rust-toolchain.toml ├── src ├── api_server.rs ├── api_server │ ├── handlers.rs │ └── routes.rs ├── config.rs ├── config │ ├── importer.rs │ ├── importer │ │ ├── clash.rs │ │ ├── merge.rs │ │ └── rhai.rs │ ├── manager.rs │ └── select_map.rs ├── lib.rs ├── log.rs ├── main.rs ├── schema.rs ├── select.rs ├── storage.rs ├── storage │ ├── file.rs │ └── memory.rs ├── tracing_helper.rs ├── util.rs └── util │ ├── debounce_stream.rs │ └── exit_stream.rs └── tests ├── relay_clash.yml └── relay_rdp.yml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["--cfg", "tokio_unstable"] 3 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/rust/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Debian OS version (use bullseye on local arm64/Apple Silicon): buster, bullseye 4 | ARG VARIANT="buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT} 6 | 7 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 8 | && apt-get -y install --no-install-recommends libpcap-dev 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/rust 3 | { 4 | "name": "Rust", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "args": { 8 | // Use the VARIANT arg to pick a Debian OS version: buster, bullseye 9 | // Use bullseye when on local on arm64/Apple Silicon. 10 | "VARIANT": "buster" 11 | } 12 | }, 13 | "runArgs": [ 14 | "--init", 15 | "--cap-add=SYS_PTRACE", 16 | "--security-opt", 17 | "seccomp=unconfined" 18 | ], 19 | // Set *default* container specific settings.json values on container create. 20 | "settings": { 21 | "lldb.executable": "/usr/bin/lldb", 22 | // VS Code don't watch files under ./target 23 | "files.watcherExclude": { 24 | "**/target/**": true 25 | }, 26 | "rust-analyzer.checkOnSave.command": "clippy" 27 | }, 28 | // Add the IDs of extensions you want installed when the container is created. 29 | "extensions": [ 30 | "vadimcn.vscode-lldb", 31 | "mutantdino.resourcemonitor", 32 | "matklad.rust-analyzer", 33 | "tamasfe.even-better-toml", 34 | "serayuzgur.crates" 35 | ], 36 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 37 | // "forwardPorts": [], 38 | // Use 'postCreateCommand' to run commands after the container is created. 39 | // "postCreateCommand": "rustc --version", 40 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 41 | "remoteUser": "vscode", 42 | "features": { 43 | "git": "os-provided", 44 | "git-lfs": "latest" 45 | } 46 | } -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | name: Deploy docs to github pages 3 | on: 4 | push: 5 | branches: [main, feature-docs] 6 | 7 | jobs: 8 | build: 9 | name: Build Docusaurus 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - uses: pnpm/action-setup@v4 16 | with: 17 | version: 9 18 | - name: Use Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: "20" 22 | cache: pnpm 23 | cache-dependency-path: docs/pnpm-lock.yaml 24 | - name: Install dependencies 25 | run: | 26 | cd docs 27 | pnpm install --frozen-lockfile 28 | - name: Build website 29 | run: | 30 | cd docs 31 | pnpm build 32 | - name: Upload Build Artifact 33 | uses: actions/upload-pages-artifact@v3 34 | with: 35 | path: docs/build 36 | deploy: 37 | name: Deploy to GitHub Pages 38 | needs: build 39 | 40 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 41 | permissions: 42 | pages: write # to deploy to Pages 43 | id-token: write # to verify the deployment originates from an appropriate source 44 | 45 | # Deploy to the github-pages environment 46 | environment: 47 | name: github-pages 48 | url: ${{ steps.deployment.outputs.page_url }} 49 | 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Build 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest, macos-latest] 12 | include: 13 | - os: ubuntu-latest 14 | artifact_name: rabbit-digger-pro 15 | release_name: rabbit-digger-pro-linux-amd64 16 | - os: windows-latest 17 | artifact_name: rabbit-digger-pro.exe 18 | release_name: rabbit-digger-pro-windows-amd64.exe 19 | - os: macos-latest 20 | artifact_name: rabbit-digger-pro 21 | release_name: rabbit-digger-pro-macos-amd64 22 | steps: 23 | - run: git config --global core.autocrlf false 24 | - uses: actions/checkout@v2 25 | with: 26 | submodules: recursive 27 | - name: Cache Cargo 28 | uses: actions/cache@v1 29 | with: 30 | path: ~/.cargo 31 | key: ${{ matrix.os }}-stable-cargo-v1 32 | restore-keys: | 33 | ${{ matrix.os }}-stable-cargo-v1 34 | - name: Setup toolchain 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: stable 38 | target: ${{ matrix.target }} 39 | override: true 40 | - name: Run tests 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: test 44 | args: --all --exclude raw 45 | - name: Build release 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: build 49 | args: --release 50 | - name: Upload binaries to release 51 | uses: svenstaro/upload-release-action@v1-release 52 | if: startsWith(github.ref, 'refs/tags/') 53 | with: 54 | repo_token: ${{ secrets.GITHUB_TOKEN }} 55 | file: target/release/${{ matrix.artifact_name }} 56 | asset_name: ${{ matrix.release_name }} 57 | tag: ${{ github.ref }} 58 | 59 | cross_compile: 60 | name: Cross compile 61 | runs-on: ubuntu-latest 62 | strategy: 63 | matrix: 64 | target: 65 | - armv7-unknown-linux-gnueabihf 66 | - aarch64-unknown-linux-musl 67 | - i686-unknown-linux-musl 68 | - x86_64-unknown-linux-musl 69 | steps: 70 | - uses: actions/checkout@v2 71 | with: 72 | submodules: recursive 73 | - name: Cache Cargo 74 | uses: actions/cache@v1 75 | with: 76 | path: ~/.cargo 77 | key: ${{ matrix.target }}-stable-cargo-v1 78 | restore-keys: | 79 | ${{ matrix.target }}-stable-cargo-v1 80 | - name: Setup toolchain 81 | uses: actions-rs/toolchain@v1 82 | with: 83 | toolchain: stable 84 | target: ${{ matrix.target }} 85 | override: true 86 | - name: Build release 87 | uses: actions-rs/cargo@v1 88 | with: 89 | use-cross: true 90 | command: build 91 | args: --release --target=${{ matrix.target }} 92 | - name: Upload binaries to release 93 | uses: svenstaro/upload-release-action@v1-release 94 | if: startsWith(github.ref, 'refs/tags/') 95 | with: 96 | repo_token: ${{ secrets.GITHUB_TOKEN }} 97 | file: target/${{ matrix.target }}/release/rabbit-digger-pro 98 | asset_name: rabbit-digger-pro-${{ matrix.target }} 99 | tag: ${{ github.ref }} 100 | -------------------------------------------------------------------------------- /.github/workflows/update_schema.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Update schema 7 | 8 | jobs: 9 | update_schema: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: clone schema 13 | run: | 14 | cd /tmp/ 15 | git clone https://${{ secrets.API_TOKEN_GITHUB }}@github.com/rabbit-digger/schema 16 | - uses: actions/checkout@v2 17 | with: 18 | submodules: recursive 19 | - name: Cache Cargo 20 | uses: actions/cache@v1 21 | with: 22 | path: ~/.cargo 23 | key: ${{ matrix.os }}-stable-cargo-v1 24 | restore-keys: | 25 | ${{ matrix.os }}-stable-cargo-v1 26 | - name: Setup toolchain 27 | uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | target: ${{ matrix.target }} 31 | override: true 32 | - name: Generate schema 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: run 36 | args: -- generate-schema /tmp/rabbit-digger-pro-schema.json 37 | - name: Update git repo 38 | run: | 39 | cd /tmp/schema/ 40 | cp /tmp/rabbit-digger-pro-schema.json . 41 | 42 | git add . 43 | git config user.email "spacemeowx2@gmail.com" 44 | git config user.name "spacemeowx2" 45 | git commit -m "update at `date`" && git push || true 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | /*.yaml 4 | /generated 5 | *.rd.yml 6 | *.dat 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "blob"] 2 | path = blob 3 | url = https://github.com/rabbit-digger/blob.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /.rusty-hook.toml: -------------------------------------------------------------------------------- 1 | [hooks] 2 | pre-commit = "cargo fmt -- --check" 3 | pre-push = "cargo test --all" 4 | 5 | [logging] 6 | verbose = true 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rabbit-digger-pro 2 | 3 | ![logo](https://user-images.githubusercontent.com/8019167/219358254-dd507c1e-99af-4a70-9081-59e44794edc2.png) 4 | 5 | [![codecov][codecov-badge]][codecov-url] 6 | [![MIT licensed][mit-badge]][mit-url] 7 | [![Build Status][actions-badge]][actions-url] 8 | 9 | [codecov-badge]: https://codecov.io/gh/rabbit-digger/rabbit-digger-pro/branch/main/graph/badge.svg?token=VM9N0IGMWE 10 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 11 | [actions-badge]: https://github.com/rabbit-digger/rabbit-digger-pro/workflows/Build/badge.svg 12 | 13 | [codecov-url]: https://codecov.io/gh/rabbit-digger/rabbit-digger-pro 14 | [mit-url]: https://github.com/rabbit-digger/rabbit-digger-pro/blob/master/LICENSE 15 | [actions-url]: https://github.com/rabbit-digger/rabbit-digger-pro/actions?query=workflow%3ABuild+branch%3Amain 16 | 17 | All-in-one proxy written in Rust. 18 | 19 | ## Features 20 | 21 | * Hot reloading: Apply changes without restart the program. 22 | * Flexible configuration: proxies can be nested at will, supporting TCP and UDP. 23 | * JSON Schema generation: no documentation needed, write configuration directly from code completion. 24 | 25 | ### Supported Protocol 26 | 27 | * Shadowsocks 28 | * Trojan 29 | * HTTP 30 | * Socks5 31 | * obfs(http_simple) 32 | 33 | ### Supported Server Protocol 34 | 35 | * Socks5 36 | * HTTP 37 | * http+socks5 on the same port 38 | * Shadowsocks 39 | 40 | ## crates 41 | 42 | * rd-derive 43 | 44 | Used to conveniently define the Config structure. 45 | 46 | * rd-std 47 | 48 | Some basic net and server, such as rule, HTTP and Socks5. 49 | 50 | * rd-interface 51 | 52 | Interface defines of rabbit-digger's plugin. 53 | 54 | ## Credits 55 | 56 | * [shadowsocks-rust](https://github1s.com/shadowsocks/shadowsocks-rust) 57 | * [smoltcp](https://github.com/smoltcp-rs/smoltcp) 58 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | ``` 4 | ./bench.sh 5 | ``` 6 | 7 | And the result will be in the `benchmark/results` directory. 8 | -------------------------------------------------------------------------------- /benchmark/bench.sh: -------------------------------------------------------------------------------- 1 | iperf3 -s & 2 | cargo run --release -- -c ./benchmark.yaml & 3 | 4 | 5 | echo "Waiting for iperf3 to start" 6 | while ! nc -z 127.0.0.1 5201; do 7 | sleep 1 8 | done 9 | echo "iperf3 is started" 10 | 11 | echo "Waiting for RDP to start" 12 | 13 | while ! nc -z 127.0.0.1 20000; do 14 | sleep 1 15 | done 16 | 17 | echo "Start benchmark" 18 | 19 | mkdir -p ./results 20 | 21 | TCP_FLAGS="-t 10" 22 | UDP_FLAGS="-u -b 1G -l 1000" 23 | 24 | iperf3 -c 127.0.0.1 $TCP_FLAGS > ./results/iperf3.txt 25 | iperf3 -c 127.0.0.1 $UDP_FLAGS >> ./results/iperf3.txt 26 | 27 | iperf3 -c 127.0.0.1 -p 20000 $TCP_FLAGS > ./results/iperf3-forward.txt 28 | iperf3 -c 127.0.0.1 -p 20000 $UDP_FLAGS >> ./results/iperf3-forward.txt 29 | 30 | iperf3 -c 127.0.0.1 -p 20001 $TCP_FLAGS > ./results/iperf3-aes-128-gcm.txt 31 | iperf3 -c 127.0.0.1 -p 20001 $UDP_FLAGS >> ./results/iperf3-aes-128-gcm.txt 32 | 33 | iperf3 -c 127.0.0.1 -p 20002 $TCP_FLAGS > ./results/iperf3-aes-256-gcm.txt 34 | iperf3 -c 127.0.0.1 -p 20002 $UDP_FLAGS >> ./results/iperf3-aes-256-gcm.txt 35 | 36 | iperf3 -c 127.0.0.1 -p 20003 $TCP_FLAGS > ./results/iperf3-chacha20-ietf-poly1305.txt 37 | iperf3 -c 127.0.0.1 -p 20003 $UDP_FLAGS >> ./results/iperf3-chacha20-ietf-poly1305.txt 38 | 39 | iperf3 -c 127.0.0.1 -p 20004 $TCP_FLAGS > ./results/iperf3-rc4-md5.txt 40 | iperf3 -c 127.0.0.1 -p 20004 $UDP_FLAGS >> ./results/iperf3-rc4-md5.txt 41 | 42 | 43 | kill -INT %1 44 | kill -9 %2 45 | 46 | echo "benchmark finished" 47 | 48 | jobs 49 | -------------------------------------------------------------------------------- /benchmark/benchmark.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/rabbit-digger/schema/master/rabbit-digger-pro-schema.json 2 | ss-common: &ss-common 3 | type: shadowsocks 4 | password: bench 5 | udp: true 6 | forward-common: &forward-common 7 | type: forward 8 | target: 127.0.0.1:5201 9 | udp: true 10 | 11 | server: 12 | ss-server-aes-128-gcm: 13 | <<: *ss-common 14 | cipher: aes-128-gcm 15 | bind: 127.0.0.1:30001 16 | ss-server-aes-256-gcm: 17 | <<: *ss-common 18 | cipher: aes-256-gcm 19 | bind: 127.0.0.1:30002 20 | ss-server-chacha20-ietf-poly1305: 21 | <<: *ss-common 22 | cipher: chacha20-ietf-poly1305 23 | bind: 127.0.0.1:30003 24 | ss-server-rc4-md5: 25 | <<: *ss-common 26 | cipher: rc4-md5 27 | bind: 127.0.0.1:30004 28 | 29 | iperf3-forward: 30 | <<: *forward-common 31 | bind: 127.0.0.1:20000 32 | forward-ss-aes-128-gcm: 33 | <<: *forward-common 34 | bind: 127.0.0.1:20001 35 | net: 36 | <<: *ss-common 37 | cipher: aes-128-gcm 38 | server: 127.0.0.1:30001 39 | forward-ss-aes-256-gcm: 40 | <<: *forward-common 41 | bind: 127.0.0.1:20002 42 | net: 43 | <<: *ss-common 44 | cipher: aes-256-gcm 45 | server: 127.0.0.1:30002 46 | forward-chacha20-ietf-poly1305: 47 | <<: *forward-common 48 | bind: 127.0.0.1:20003 49 | net: 50 | <<: *ss-common 51 | cipher: chacha20-ietf-poly1305 52 | server: 127.0.0.1:30003 53 | forward-rc4-md5: 54 | <<: *forward-common 55 | bind: 127.0.0.1:20004 56 | net: 57 | <<: *ss-common 58 | cipher: rc4-md5 59 | server: 127.0.0.1:30004 60 | -------------------------------------------------------------------------------- /benchmark/results/iperf3-aes-128-gcm.txt: -------------------------------------------------------------------------------- 1 | Connecting to host 127.0.0.1, port 20001 2 | [ 5] local 127.0.0.1 port 40780 connected to 127.0.0.1 port 20001 3 | [ ID] Interval Transfer Bitrate Retr Cwnd 4 | [ 5] 0.00-1.00 sec 636 MBytes 5.33 Gbits/sec 0 2.31 MBytes 5 | [ 5] 1.00-2.00 sec 602 MBytes 5.05 Gbits/sec 0 2.31 MBytes 6 | [ 5] 2.00-3.00 sec 598 MBytes 5.01 Gbits/sec 0 2.31 MBytes 7 | [ 5] 3.00-4.00 sec 601 MBytes 5.04 Gbits/sec 0 2.31 MBytes 8 | [ 5] 4.00-5.00 sec 620 MBytes 5.20 Gbits/sec 0 2.31 MBytes 9 | [ 5] 5.00-6.00 sec 628 MBytes 5.26 Gbits/sec 0 2.31 MBytes 10 | [ 5] 6.00-7.00 sec 611 MBytes 5.13 Gbits/sec 0 2.31 MBytes 11 | [ 5] 7.00-8.00 sec 614 MBytes 5.15 Gbits/sec 0 2.31 MBytes 12 | [ 5] 8.00-9.00 sec 625 MBytes 5.24 Gbits/sec 0 2.31 MBytes 13 | [ 5] 9.00-10.00 sec 615 MBytes 5.16 Gbits/sec 0 2.31 MBytes 14 | - - - - - - - - - - - - - - - - - - - - - - - - - 15 | [ ID] Interval Transfer Bitrate Retr 16 | [ 5] 0.00-10.00 sec 6.01 GBytes 5.16 Gbits/sec 0 sender 17 | [ 5] 0.00-10.00 sec 5.99 GBytes 5.15 Gbits/sec receiver 18 | 19 | iperf Done. 20 | Connecting to host 127.0.0.1, port 20001 21 | [ 5] local 127.0.0.1 port 52247 connected to 127.0.0.1 port 20001 22 | [ ID] Interval Transfer Bitrate Total Datagrams 23 | [ 5] 0.00-1.00 sec 119 MBytes 1000 Mbits/sec 124951 24 | [ 5] 1.00-2.00 sec 119 MBytes 1000 Mbits/sec 125040 25 | [ 5] 2.00-3.00 sec 119 MBytes 1000 Mbits/sec 124926 26 | [ 5] 3.00-4.00 sec 119 MBytes 1.00 Gbits/sec 125001 27 | [ 5] 4.00-5.00 sec 119 MBytes 999 Mbits/sec 124990 28 | [ 5] 5.00-6.00 sec 119 MBytes 1.00 Gbits/sec 125037 29 | [ 5] 6.00-7.00 sec 119 MBytes 1000 Mbits/sec 124942 30 | [ 5] 7.00-8.00 sec 119 MBytes 1.00 Gbits/sec 125064 31 | [ 5] 8.00-9.00 sec 119 MBytes 1.00 Gbits/sec 124987 32 | [ 5] 9.00-10.00 sec 119 MBytes 1.00 Gbits/sec 125054 33 | - - - - - - - - - - - - - - - - - - - - - - - - - 34 | [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams 35 | [ 5] 0.00-10.00 sec 1.16 GBytes 1000 Mbits/sec 0.000 ms 0/1249992 (0%) sender 36 | [ 5] 0.00-10.00 sec 958 MBytes 804 Mbits/sec 0.008 ms 245176/1249853 (20%) receiver 37 | 38 | iperf Done. 39 | -------------------------------------------------------------------------------- /benchmark/results/iperf3-aes-256-gcm.txt: -------------------------------------------------------------------------------- 1 | Connecting to host 127.0.0.1, port 20002 2 | [ 5] local 127.0.0.1 port 40812 connected to 127.0.0.1 port 20002 3 | [ ID] Interval Transfer Bitrate Retr Cwnd 4 | [ 5] 0.00-1.00 sec 565 MBytes 4.74 Gbits/sec 0 1.44 MBytes 5 | [ 5] 1.00-2.00 sec 562 MBytes 4.72 Gbits/sec 0 1.44 MBytes 6 | [ 5] 2.00-3.00 sec 539 MBytes 4.52 Gbits/sec 0 1.44 MBytes 7 | [ 5] 3.00-4.00 sec 538 MBytes 4.51 Gbits/sec 0 1.44 MBytes 8 | [ 5] 4.00-5.00 sec 544 MBytes 4.56 Gbits/sec 0 1.44 MBytes 9 | [ 5] 5.00-6.00 sec 568 MBytes 4.76 Gbits/sec 0 1.44 MBytes 10 | [ 5] 6.00-7.00 sec 564 MBytes 4.73 Gbits/sec 0 1.44 MBytes 11 | [ 5] 7.00-8.00 sec 561 MBytes 4.71 Gbits/sec 0 1.44 MBytes 12 | [ 5] 8.00-9.00 sec 555 MBytes 4.65 Gbits/sec 0 1.44 MBytes 13 | [ 5] 9.00-10.00 sec 580 MBytes 4.87 Gbits/sec 0 1.44 MBytes 14 | - - - - - - - - - - - - - - - - - - - - - - - - - 15 | [ ID] Interval Transfer Bitrate Retr 16 | [ 5] 0.00-10.00 sec 5.44 GBytes 4.68 Gbits/sec 0 sender 17 | [ 5] 0.00-10.00 sec 5.43 GBytes 4.66 Gbits/sec receiver 18 | 19 | iperf Done. 20 | Connecting to host 127.0.0.1, port 20002 21 | [ 5] local 127.0.0.1 port 52681 connected to 127.0.0.1 port 20002 22 | [ ID] Interval Transfer Bitrate Total Datagrams 23 | [ 5] 0.00-1.00 sec 119 MBytes 999 Mbits/sec 124916 24 | [ 5] 1.00-2.00 sec 119 MBytes 1.00 Gbits/sec 125079 25 | [ 5] 2.00-3.00 sec 119 MBytes 998 Mbits/sec 124708 26 | [ 5] 3.00-4.00 sec 119 MBytes 1.00 Gbits/sec 125189 27 | [ 5] 4.00-5.00 sec 119 MBytes 1000 Mbits/sec 125020 28 | [ 5] 5.00-6.00 sec 119 MBytes 1.00 Gbits/sec 125033 29 | [ 5] 6.00-7.00 sec 119 MBytes 1000 Mbits/sec 125016 30 | [ 5] 7.00-8.00 sec 119 MBytes 999 Mbits/sec 124967 31 | [ 5] 8.00-9.00 sec 119 MBytes 1.00 Gbits/sec 125020 32 | [ 5] 9.00-10.00 sec 119 MBytes 1000 Mbits/sec 124932 33 | - - - - - - - - - - - - - - - - - - - - - - - - - 34 | [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams 35 | [ 5] 0.00-10.00 sec 1.16 GBytes 1000 Mbits/sec 0.000 ms 0/1249880 (0%) sender 36 | [ 5] 0.00-10.00 sec 939 MBytes 788 Mbits/sec 0.008 ms 264963/1249826 (21%) receiver 37 | 38 | iperf Done. 39 | -------------------------------------------------------------------------------- /benchmark/results/iperf3-chacha20-ietf-poly1305.txt: -------------------------------------------------------------------------------- 1 | Connecting to host 127.0.0.1, port 20003 2 | [ 5] local 127.0.0.1 port 54774 connected to 127.0.0.1 port 20003 3 | [ ID] Interval Transfer Bitrate Retr Cwnd 4 | [ 5] 0.00-1.00 sec 507 MBytes 4.25 Gbits/sec 0 1.25 MBytes 5 | [ 5] 1.00-2.00 sec 501 MBytes 4.21 Gbits/sec 0 1.25 MBytes 6 | [ 5] 2.00-3.00 sec 515 MBytes 4.32 Gbits/sec 0 1.25 MBytes 7 | [ 5] 3.00-4.00 sec 499 MBytes 4.18 Gbits/sec 0 1.25 MBytes 8 | [ 5] 4.00-5.00 sec 505 MBytes 4.24 Gbits/sec 0 1.25 MBytes 9 | [ 5] 5.00-6.00 sec 510 MBytes 4.28 Gbits/sec 0 1.25 MBytes 10 | [ 5] 6.00-7.00 sec 516 MBytes 4.33 Gbits/sec 0 1.25 MBytes 11 | [ 5] 7.00-8.00 sec 515 MBytes 4.32 Gbits/sec 0 1.25 MBytes 12 | [ 5] 8.00-9.00 sec 494 MBytes 4.14 Gbits/sec 0 1.25 MBytes 13 | [ 5] 9.00-10.00 sec 495 MBytes 4.15 Gbits/sec 0 1.25 MBytes 14 | - - - - - - - - - - - - - - - - - - - - - - - - - 15 | [ ID] Interval Transfer Bitrate Retr 16 | [ 5] 0.00-10.00 sec 4.94 GBytes 4.24 Gbits/sec 0 sender 17 | [ 5] 0.00-10.00 sec 4.92 GBytes 4.23 Gbits/sec receiver 18 | 19 | iperf Done. 20 | Connecting to host 127.0.0.1, port 20003 21 | [ 5] local 127.0.0.1 port 37974 connected to 127.0.0.1 port 20003 22 | [ ID] Interval Transfer Bitrate Total Datagrams 23 | [ 5] 0.00-1.00 sec 119 MBytes 1000 Mbits/sec 124972 24 | [ 5] 1.00-2.00 sec 119 MBytes 1000 Mbits/sec 124998 25 | [ 5] 2.00-3.00 sec 119 MBytes 1000 Mbits/sec 125023 26 | [ 5] 3.00-4.00 sec 119 MBytes 999 Mbits/sec 124907 27 | [ 5] 4.00-5.00 sec 119 MBytes 1.00 Gbits/sec 125010 28 | [ 5] 5.00-6.00 sec 119 MBytes 1000 Mbits/sec 125024 29 | [ 5] 6.00-7.00 sec 119 MBytes 999 Mbits/sec 124944 30 | [ 5] 7.00-8.00 sec 119 MBytes 1.00 Gbits/sec 125037 31 | [ 5] 8.00-9.00 sec 119 MBytes 999 Mbits/sec 124972 32 | [ 5] 9.00-10.00 sec 119 MBytes 1.00 Gbits/sec 125100 33 | - - - - - - - - - - - - - - - - - - - - - - - - - 34 | [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams 35 | [ 5] 0.00-10.00 sec 1.16 GBytes 1000 Mbits/sec 0.000 ms 0/1249987 (0%) sender 36 | [ 5] 0.00-10.00 sec 906 MBytes 760 Mbits/sec 0.006 ms 300177/1249856 (24%) receiver 37 | 38 | iperf Done. 39 | -------------------------------------------------------------------------------- /benchmark/results/iperf3-forward.txt: -------------------------------------------------------------------------------- 1 | Connecting to host 127.0.0.1, port 20000 2 | [ 5] local 127.0.0.1 port 34404 connected to 127.0.0.1 port 20000 3 | [ ID] Interval Transfer Bitrate Retr Cwnd 4 | [ 5] 0.00-1.00 sec 918 MBytes 7.69 Gbits/sec 0 1.50 MBytes 5 | [ 5] 1.00-2.00 sec 882 MBytes 7.40 Gbits/sec 0 1.50 MBytes 6 | [ 5] 2.00-3.00 sec 914 MBytes 7.67 Gbits/sec 0 1.50 MBytes 7 | [ 5] 3.00-4.00 sec 905 MBytes 7.59 Gbits/sec 0 1.50 MBytes 8 | [ 5] 4.00-5.00 sec 916 MBytes 7.69 Gbits/sec 0 1.50 MBytes 9 | [ 5] 5.00-6.00 sec 900 MBytes 7.55 Gbits/sec 0 1.50 MBytes 10 | [ 5] 6.00-7.00 sec 899 MBytes 7.54 Gbits/sec 0 1.50 MBytes 11 | [ 5] 7.00-8.00 sec 908 MBytes 7.61 Gbits/sec 0 1.50 MBytes 12 | [ 5] 8.00-9.00 sec 949 MBytes 7.96 Gbits/sec 0 1.50 MBytes 13 | [ 5] 9.00-10.00 sec 905 MBytes 7.59 Gbits/sec 0 1.50 MBytes 14 | - - - - - - - - - - - - - - - - - - - - - - - - - 15 | [ ID] Interval Transfer Bitrate Retr 16 | [ 5] 0.00-10.00 sec 8.88 GBytes 7.63 Gbits/sec 0 sender 17 | [ 5] 0.00-10.00 sec 8.87 GBytes 7.62 Gbits/sec receiver 18 | 19 | iperf Done. 20 | Connecting to host 127.0.0.1, port 20000 21 | [ 5] local 127.0.0.1 port 57460 connected to 127.0.0.1 port 20000 22 | [ ID] Interval Transfer Bitrate Total Datagrams 23 | [ 5] 0.00-1.00 sec 119 MBytes 999 Mbits/sec 124989 24 | [ 5] 1.00-2.00 sec 119 MBytes 1000 Mbits/sec 124896 25 | [ 5] 2.00-3.00 sec 119 MBytes 1000 Mbits/sec 125026 26 | [ 5] 3.00-4.00 sec 119 MBytes 1.00 Gbits/sec 125079 27 | [ 5] 4.00-5.00 sec 119 MBytes 1.00 Gbits/sec 124977 28 | [ 5] 5.00-6.00 sec 119 MBytes 1000 Mbits/sec 124975 29 | [ 5] 6.00-7.00 sec 119 MBytes 999 Mbits/sec 124919 30 | [ 5] 7.00-8.00 sec 119 MBytes 1.00 Gbits/sec 125115 31 | [ 5] 8.00-9.00 sec 119 MBytes 1.00 Gbits/sec 124943 32 | [ 5] 9.00-10.00 sec 119 MBytes 1000 Mbits/sec 124946 33 | - - - - - - - - - - - - - - - - - - - - - - - - - 34 | [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams 35 | [ 5] 0.00-10.00 sec 1.16 GBytes 1000 Mbits/sec 0.000 ms 0/1249865 (0%) sender 36 | [ 5] 0.00-10.00 sec 1001 MBytes 840 Mbits/sec 0.006 ms 200204/1249836 (16%) receiver 37 | 38 | iperf Done. 39 | -------------------------------------------------------------------------------- /benchmark/results/iperf3-rc4-md5.txt: -------------------------------------------------------------------------------- 1 | Connecting to host 127.0.0.1, port 20004 2 | [ 5] local 127.0.0.1 port 40076 connected to 127.0.0.1 port 20004 3 | [ ID] Interval Transfer Bitrate Retr Cwnd 4 | [ 5] 0.00-1.00 sec 316 MBytes 2.65 Gbits/sec 0 1.25 MBytes 5 | [ 5] 1.00-2.00 sec 299 MBytes 2.51 Gbits/sec 0 1.25 MBytes 6 | [ 5] 2.00-3.00 sec 301 MBytes 2.53 Gbits/sec 0 1.25 MBytes 7 | [ 5] 3.00-4.00 sec 302 MBytes 2.54 Gbits/sec 0 1.25 MBytes 8 | [ 5] 4.00-5.00 sec 304 MBytes 2.55 Gbits/sec 0 1.25 MBytes 9 | [ 5] 5.00-6.00 sec 310 MBytes 2.60 Gbits/sec 0 1.25 MBytes 10 | [ 5] 6.00-7.00 sec 316 MBytes 2.65 Gbits/sec 0 1.25 MBytes 11 | [ 5] 7.00-8.00 sec 315 MBytes 2.64 Gbits/sec 0 1.25 MBytes 12 | [ 5] 8.00-9.00 sec 325 MBytes 2.73 Gbits/sec 0 1.25 MBytes 13 | [ 5] 9.00-10.00 sec 316 MBytes 2.65 Gbits/sec 0 1.25 MBytes 14 | - - - - - - - - - - - - - - - - - - - - - - - - - 15 | [ ID] Interval Transfer Bitrate Retr 16 | [ 5] 0.00-10.00 sec 3.03 GBytes 2.60 Gbits/sec 0 sender 17 | [ 5] 0.00-10.00 sec 3.02 GBytes 2.59 Gbits/sec receiver 18 | 19 | iperf Done. 20 | Connecting to host 127.0.0.1, port 20004 21 | [ 5] local 127.0.0.1 port 33367 connected to 127.0.0.1 port 20004 22 | [ ID] Interval Transfer Bitrate Total Datagrams 23 | [ 5] 0.00-1.00 sec 119 MBytes 1000 Mbits/sec 124940 24 | [ 5] 1.00-2.00 sec 119 MBytes 1000 Mbits/sec 124950 25 | [ 5] 2.00-3.00 sec 119 MBytes 1.00 Gbits/sec 125057 26 | [ 5] 3.00-4.00 sec 119 MBytes 1000 Mbits/sec 124963 27 | [ 5] 4.00-5.00 sec 119 MBytes 1.00 Gbits/sec 125086 28 | [ 5] 5.00-6.00 sec 119 MBytes 998 Mbits/sec 124696 29 | [ 5] 6.00-7.00 sec 119 MBytes 1.00 Gbits/sec 125271 30 | [ 5] 7.00-8.00 sec 119 MBytes 1000 Mbits/sec 124911 31 | [ 5] 8.00-9.00 sec 119 MBytes 1.00 Gbits/sec 125125 32 | [ 5] 9.00-10.00 sec 119 MBytes 999 Mbits/sec 124912 33 | - - - - - - - - - - - - - - - - - - - - - - - - - 34 | [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams 35 | [ 5] 0.00-10.00 sec 1.16 GBytes 1000 Mbits/sec 0.000 ms 0/1249911 (0%) sender 36 | [ 5] 0.00-10.00 sec 951 MBytes 798 Mbits/sec 0.015 ms 252388/1249806 (20%) receiver 37 | 38 | iperf Done. 39 | -------------------------------------------------------------------------------- /benchmark/results/iperf3.txt: -------------------------------------------------------------------------------- 1 | Connecting to host 127.0.0.1, port 5201 2 | [ 5] local 127.0.0.1 port 51480 connected to 127.0.0.1 port 5201 3 | [ ID] Interval Transfer Bitrate Retr Cwnd 4 | [ 5] 0.00-1.00 sec 5.44 GBytes 46.7 Gbits/sec 0 2.31 MBytes 5 | [ 5] 1.00-2.00 sec 5.30 GBytes 45.5 Gbits/sec 0 2.56 MBytes 6 | [ 5] 2.00-3.00 sec 5.34 GBytes 45.9 Gbits/sec 0 2.69 MBytes 7 | [ 5] 3.00-4.00 sec 5.46 GBytes 46.9 Gbits/sec 0 3.00 MBytes 8 | [ 5] 4.00-5.00 sec 5.41 GBytes 46.5 Gbits/sec 0 3.00 MBytes 9 | [ 5] 5.00-6.00 sec 5.43 GBytes 46.7 Gbits/sec 0 3.37 MBytes 10 | [ 5] 6.00-7.00 sec 5.52 GBytes 47.4 Gbits/sec 0 3.37 MBytes 11 | [ 5] 7.00-8.00 sec 5.39 GBytes 46.3 Gbits/sec 0 3.37 MBytes 12 | [ 5] 8.00-9.00 sec 5.51 GBytes 47.3 Gbits/sec 0 3.37 MBytes 13 | [ 5] 9.00-10.00 sec 5.43 GBytes 46.6 Gbits/sec 0 3.37 MBytes 14 | - - - - - - - - - - - - - - - - - - - - - - - - - 15 | [ ID] Interval Transfer Bitrate Retr 16 | [ 5] 0.00-10.00 sec 54.2 GBytes 46.6 Gbits/sec 0 sender 17 | [ 5] 0.00-10.00 sec 54.2 GBytes 46.6 Gbits/sec receiver 18 | 19 | iperf Done. 20 | Connecting to host 127.0.0.1, port 5201 21 | [ 5] local 127.0.0.1 port 55675 connected to 127.0.0.1 port 5201 22 | [ ID] Interval Transfer Bitrate Total Datagrams 23 | [ 5] 0.00-1.00 sec 119 MBytes 999 Mbits/sec 124987 24 | [ 5] 1.00-2.00 sec 119 MBytes 999 Mbits/sec 124907 25 | [ 5] 2.00-3.00 sec 119 MBytes 1.00 Gbits/sec 125041 26 | [ 5] 3.00-4.00 sec 119 MBytes 999 Mbits/sec 124736 27 | [ 5] 4.00-5.00 sec 119 MBytes 1.00 Gbits/sec 125188 28 | [ 5] 5.00-6.00 sec 119 MBytes 1.00 Gbits/sec 125074 29 | [ 5] 6.00-7.00 sec 119 MBytes 1.00 Gbits/sec 125027 30 | [ 5] 7.00-8.00 sec 118 MBytes 994 Mbits/sec 124216 31 | [ 5] 8.00-9.00 sec 120 MBytes 1.01 Gbits/sec 125721 32 | [ 5] 9.00-10.00 sec 119 MBytes 1.00 Gbits/sec 125039 33 | - - - - - - - - - - - - - - - - - - - - - - - - - 34 | [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams 35 | [ 5] 0.00-10.00 sec 1.16 GBytes 1000 Mbits/sec 0.000 ms 0/1249936 (0%) sender 36 | [ 5] 0.00-10.00 sec 1.16 GBytes 999 Mbits/sec 0.004 ms 1037/1249936 (0.083%) receiver 37 | 38 | iperf Done. 39 | -------------------------------------------------------------------------------- /cgr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo run --features libpcap -- $@ 3 | -------------------------------------------------------------------------------- /cgr.bat: -------------------------------------------------------------------------------- 1 | cargo run --features raw -- %* 2 | -------------------------------------------------------------------------------- /coverage/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/config/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 基本概念 6 | 7 | Rabbit Digger Pro 的配置主要由两部分组成: 8 | 9 | - `net`: 虚拟网络层,可以是: 10 | 11 | - 本地网络 12 | - 代理网络 13 | - 规则路由 14 | - 其他特殊类型 15 | 16 | - `server`: 代理服务器,支持: 17 | - HTTP 服务器 18 | - SOCKS5 服务器 19 | - 混合模式服务器 20 | 21 | ## 工作原理 22 | 23 | ```mermaid 24 | graph LR; 25 | Server(代理服务器) -->|使用| Rule(规则路由) 26 | Rule -->|分流| Proxy1(代理1) 27 | Rule -->|分流| Proxy2(代理2) 28 | Rule -->|分流| Direct(直连) 29 | Proxy1 --> Internet(互联网) 30 | Proxy2 --> Internet 31 | Direct --> Internet 32 | ``` 33 | 34 | 要点: 35 | 36 | 1. 每个 net 都有唯一的名字和类型 37 | 2. 大多数 net 可以链式组合 38 | 3. server 可以同时部署多个 39 | 4. 支持灵活的规则分流 40 | 41 | 详细配置示例请参考[配置指南](format)。 42 | -------------------------------------------------------------------------------- /docs/docs/config/format.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 配置文件格式 6 | 7 | `rabbit-digger-pro` 使用 YAML 格式的配置文件。配置文件由三个主要的根级字段组成: 8 | 9 | ## net 10 | 11 | `net` 字段用于配置代理节点和链路。每个代理节点都是一个键值对,其中键是节点名称,值是节点配置。 12 | 13 | ```yaml 14 | net: 15 | # Shadowsocks 代理节点 16 | my_ss: 17 | type: shadowsocks 18 | server: example.com:1234 19 | cipher: aes-256-cfb 20 | password: password 21 | 22 | # HTTP 代理节点 23 | http_proxy: 24 | type: http 25 | server: 127.0.0.1 26 | port: 8080 27 | ``` 28 | 29 | ## server 30 | 31 | `server` 字段用于配置本地服务器,比如 HTTP/SOCKS5 代理服务器。 32 | 33 | ```yaml 34 | server: 35 | # 混合模式代理服务器 36 | mixed: 37 | type: http+socks5 38 | bind: 127.0.0.1:1080 39 | net: my_ss # 使用上面定义的 my_ss 节点 40 | 41 | # HTTP 代理服务器 42 | http: 43 | type: http 44 | bind: 127.0.0.1:8080 45 | net: local # 使用直连节点 46 | ``` 47 | 48 | ## import 49 | 50 | `import` 字段用于导入其他配置文件或 Clash 配置。rabbit-digger-pro 会根据 `import` 的顺序依次导入配置文件。 51 | 52 | ```yaml 53 | import: 54 | # 导入本地配置文件,合并到当前配置 55 | - type: merge 56 | source: 57 | path: ./local-config.yaml 58 | 59 | # 导入 Clash 配置 60 | - type: clash 61 | source: 62 | poll: 63 | url: "https://example.com/clash-config.yaml" 64 | interval: 86400 65 | ``` 66 | 67 | ### 完整示例 68 | 69 | ```yaml 70 | net: 71 | # Shadowsocks 代理节点 72 | my_ss: 73 | type: shadowsocks 74 | server: example.com:1234 75 | cipher: aes-256-cfb 76 | password: password 77 | 78 | # HTTP 代理节点 79 | http_proxy: 80 | type: http 81 | server: 127.0.0.1 82 | port: 8080 83 | 84 | server: 85 | # 混合模式代理服务器 86 | mixed: 87 | type: http+socks5 88 | bind: 127.0.0.1:1080 89 | net: my_ss # 使用上面定义的 my_ss 节点 90 | 91 | # HTTP 代理服务器 92 | http: 93 | type: http 94 | bind: 127.0.0.1:8080 95 | net: local # 使用直连节点 96 | 97 | import: 98 | # 导入本地配置文件,合并到当前配置 99 | - type: merge 100 | source: 101 | path: ./local-config.yaml 102 | 103 | # 导入 Clash 配置 104 | - type: clash 105 | source: 106 | poll: 107 | url: "https://example.com/clash-config.yaml" 108 | interval: 86400 109 | ``` 110 | -------------------------------------------------------------------------------- /docs/docs/inside/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | 5 | # Inside 6 | 7 | `rabbit-digger` 内部的秘密 🐰... 8 | 9 | ## Net 10 | 11 | `Net` 是 `rabbit-digger` 的核心概念. 每个代理协议都是根据一个配置(如代理服务器地址, 认证方式, 基于的 `Net`) 构造一个新的 `Net`. 这个 `Net` 提供了 `tcp_connect` 和 `udp_bind`, 对使用者隐藏了服务器的连接细节, 能够让使用者直接调用 `tcp_connect` 和 `udp_bind`. 12 | 13 | `Net` 的实现者不应该使用异步运行时提供的 `TcpStream` 和 `UdpSocket` 来连接代理服务器. 而是应该在 `Config` 中声明 `NetRef`, 然后使用这个 `Net` 来连接代理服务器. 14 | 15 | 因此, 每个代理协议都能够互相嵌套, 自然的实现了代理链. 16 | 17 | ## NetRef 18 | 19 | `NetRef` 是一个 `enum`, 有 `String` 和 `Net` 两种状态. 当 `Config` 从文件读入时, `NetRef` 是一个未解析的字符串. 而 `rabbit-digger` 会根据引用关系一次将 `NetRef` 解析成 `Net` 实例, 然后传给 `NetFactory::new`. 20 | 21 | ## ExternalFile 22 | 23 | `ExternalFile` 可用在 `Config` 中. 代表着这个字段是一个外部的文件. `ExternalFile` 可以是文件, 也可以是 `Url`. 当 `ExternalFile` 是文件且 `watch` 为 `true` 时, `Net` 会在文件变更时被重建. 当 `Url` 和 `interval` 被设置时, 文件会被轮询, 并且在改变时重建 `Net`. 24 | 25 | ## Config 处理流 26 | 27 | 所有 `Config` 类型都实现了 `Config` trait, `rabbit-digger` 会在加载 `Net` 时调用 `Config::visit` 来访问内部的字段, 并填入所有的 `NetRef`, `ExternalFile`. 在填入 `ExternalFile` 的时候会记录所有使用到的文件, 并在文件变动的时候重新构建 `Net`. 28 | 29 | ```mermaid 30 | flowchart TD 31 | input(Config.yml) 32 | mkctx[创建配置上下文, 用于保存 Config 依赖的文件] 33 | import[处理 Import 字段] 34 | build[构造 Net 和 Server] 35 | cond{依赖的文件 36 | 是否改变?} 37 | run_server[运行 Server] 38 | 39 | input --> mkctx --> import --> build --> run_server --> cond 40 | cond --> |Yes| mkctx 41 | cond --> |No| run_server 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/docs/tutorial-basics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorial - Basics", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "5 minutes to learn the most important Docusaurus concepts." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/tutorial/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 下载安装 6 | 7 | 前往 [Release 页面](https://github.com/spacemeowx2/rabbit-digger-pro/releases) 下载对应平台的二进制文件. 8 | 9 | - Windows 用户: `rabbit-digger-pro-windows-amd64.exe` 10 | - Mac 用户: `rabbit-digger-pro-macos-amd64` 11 | - Linux 用户: `rabbit-digger-pro-x86_64-unknown-linux-musl` 12 | 13 | 下载后, 将二进制文件放到任意目录下. 14 | 15 | 本文档之后会使用 `./rabbit-digger-pro` 作为命令调用. 因此可以将文件重命名为 `rabbit-digger-pro`. 16 | 17 | ## 命令行参数说明 18 | 19 | ```bash 20 | Usage: rabbit-digger-pro.exe [OPTIONS] [COMMAND] 21 | 22 | Commands: 23 | generate-schema 生成 JSON Schema 配置模板,若未指定路径则输出到标准输出 24 | server 以服务器模式运行 25 | help 显示帮助信息 26 | 27 | Options: 28 | -c, --config 配置文件路径 [环境变量: RD_CONFIG=] [默认: config.yaml] 29 | -b, --bind HTTP API 监听地址 [环境变量: RD_BIND=] 30 | --access-token API 访问令牌 [环境变量: RD_ACCESS_TOKEN=] 31 | --web-ui Web 界面目录路径 [环境变量: RD_WEB_UI=] 32 | --write-config 将生成的配置写入指定路径 33 | -h, --help 显示帮助信息 34 | ``` 35 | 36 | ### 常用命令示例 37 | 38 | 1. 使用指定配置文件启动: 39 | 40 | ```bash 41 | ./rabbit-digger-pro -c my-config.yaml 42 | ``` 43 | 44 | 2. 生成 JSON Schema: 45 | 46 | ```bash 47 | ./rabbit-digger-pro generate-schema > schema.json 48 | ``` 49 | 50 | 3. 启动 HTTP API 服务: 51 | 52 | ```bash 53 | ./rabbit-digger-pro -b 127.0.0.1:8080 server 54 | ``` 55 | 56 | 4. 设置访问令牌并启动: 57 | 58 | ```bash 59 | ./rabbit-digger-pro --access-token your-token server 60 | ``` 61 | 62 | ### 环境变量 63 | 64 | 所有命令行参数都可以通过环境变量设置: 65 | 66 | - `RD_CONFIG`: 配置文件路径 67 | - `RD_BIND`: HTTP API 监听地址 68 | - `RD_ACCESS_TOKEN`: API 访问令牌 69 | - `RD_WEB_UI`: Web 界面目录路径 70 | 71 | 环境变量的优先级低于命令行参数。 72 | -------------------------------------------------------------------------------- /docs/docs/tutorial/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 介绍 6 | 7 | `rabbit-digger-pro` 是由 [Rust](https://www.rust-lang.org/) 编写的代理软件. 8 | 9 | ### 核心特性 10 | 11 | - **热重载支持** - 配置文件修改后实时生效,无需重启程序 12 | - **灵活的配置系统** - 支持代理的任意嵌套组合,完整支持 TCP 和 UDP 转发 13 | - **JSON Schema 支持** - 提供配置文件的代码补全功能,无需查阅文档即可编写 14 | 15 | ### 协议支持 16 | 17 | - **多协议兼容** - 支持 Shadowsocks、Trojan、HTTP、SOCKS5 等主流代理协议 18 | - **规则路由系统** - 强大的分流规则引擎,支持域名、IP、GeoIP 等多种匹配方式 19 | - **Clash 配置兼容** - 可直接导入现有的 Clash 配置文件,无缝迁移 20 | 21 | ### 开发友好 22 | 23 | - **API 接口** - 提供 HTTP API 接口,支持程序化控制和状态监控 24 | - **插件系统** - 可扩展的插件架构,支持自定义协议和功能开发 25 | - **跨平台支持** - 支持 Windows、Linux、macOS 等主流操作系统 26 | 27 | ### 其他亮点 28 | 29 | - 配置文件支持 YAML 格式 30 | - 内置 DNS 解析功能,支持 DNS over HTTPS 31 | - 提供详细的连接日志和统计信息 32 | - 低资源占用,性能优异 33 | 34 | ## 支持的代理协议 35 | 36 | - Shadowsocks 37 | - Trojan 38 | - HTTP 39 | - Socks5 40 | - obfs(http_simple) 41 | 42 | ## 支持的服务器协议 43 | 44 | - Socks5 45 | - HTTP 46 | - http+socks on the same port 47 | - Shadowsocks 48 | -------------------------------------------------------------------------------- /docs/docs/tutorial/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 快速上手 6 | 7 | `Rabbit Digger Pro` 是一个命令行程序. 在不提供参数的情况下, 它会读取工作目录下的 `config.yaml` 作为配置文件, 然后开始运行. 如果需要指定配置文件为其他位置, 可以传入参数 `-c`. 8 | 9 | ```shell 10 | ./rabbit-digger-pro -c config.yaml 11 | ``` 12 | 13 | ## 单 Shadowsocks 配置 14 | 15 | 这个配置文件会在本机监听 `10800` 端口, 并且将传入的代理请求通过 `Shadowsocks` 协议转发到代理服务器. 16 | 17 | 其中, `example.com:1234` 是远程服务器的地址. 成功运行后, 本机的 `10800` 端口可以接受 `HTTP` 协议和 `SOCKS 5` 协议的代理请求. 18 | 19 | `ss_net` 和 `mixed` 可以替换成任意字符串, 它们分别代表这个代理和服务的名字. 20 | 21 | 如果你修改了 `net` 中的 `ss_net`, 别忘了同时修改 `server` / `mixed` / `net` 中的 `ss_net`. 22 | 23 | `config.yaml`: 24 | 25 | ```yaml 26 | net: 27 | ss_net: 28 | type: shadowsocks 29 | server: example.com:1234 30 | cipher: aes-256-cfb 31 | password: password 32 | udp: true 33 | server: 34 | mixed: 35 | type: http+socks5 36 | bind: 127.0.0.1:10800 37 | net: ss_net 38 | ``` 39 | 40 | ## Clash 订阅 41 | 42 | `Rabbit Digger Pro` 支持部分 `Clash` 规则的导入. 43 | 44 | 在这个样例中, `Rabbit Digger Pro` 会从 `url` 中读取规则和代理, 并将其加入 `net`. 45 | `Clash` 中的所有代理会以相同的名字导入这个配置文件中, 而规则会以 `clash_rule` 命名. 46 | 47 | 在配置文件的其他地方, 你可以使用由 `import` 导入的代理和规则. 例如在 `server` 中引用 `clash_rule`. 48 | 49 | ::: warning 50 | 请注意, `import` 阶段对 `url` 的请求并不会经过 `Rabbit Digger Pro` 中的任何代理. 如果有通过代理访问的需求, 需要设置环境变量 `http_proxy`, `https_proxy`. 51 | ::: 52 | 53 | ```yaml 54 | server: 55 | mixed: 56 | type: http+socks5 57 | bind: 127.0.0.1:10800 58 | net: clash_rule 59 | import: 60 | - type: clash 61 | poll: 62 | # Clash 配置地址 63 | url: https://example.com/subscribe.yaml 64 | # 每过 86400 秒, 也就是 1 天更新一次 65 | interval: 86400 66 | # 生成的规则名 67 | rule_name: clash_rule 68 | ``` 69 | 70 | 如果你的 `Clash` 文件是本地文件, 可以将 `import` 字段改为如下配置: 71 | 72 | ```yaml 73 | import: 74 | - type: clash 75 | path: /path/to/subscribe.yaml 76 | rule_name: clash_rule 77 | ``` 78 | 79 | ## 带规则的多出口代理 80 | 81 | 在这个样例中, 假设你有 `us`, `jp` 两个出口, `us` 是 `trojan` 协议, `jp` 是 `shadowsocks` 协议. 82 | 83 | 我们希望在连接发生时, 通过判断域名来走不同的出口: 84 | 85 | - 当域名以 `google.com` `结尾`时, 通过 `jp` 连接. 86 | - 当域名中`包含` `twitter` 时, 通过 `us` 连接. 87 | - 其他情况, 通过 `local` 连接. 88 | 89 | ::: tip 90 | `local` 代表使用本机直接连接. 即使你没有在 `net` 中声明也默认存在. 然而你还是可以通过在 `net` 中声明 `local` 来覆盖这个默认行为. 91 | ::: 92 | 93 | ```yaml 94 | # yaml-language-server: $schema=https://rabbit-digger.github.io/schema/rabbit-digger-pro-schema.json 95 | net: 96 | us: 97 | type: trojan 98 | server: us.example.com:443 99 | sni: us.example.com 100 | password: "uspassword" 101 | udp: true 102 | jp: 103 | type: shadowsocks 104 | server: jp.example.com:1234 105 | cipher: aes-256-cfb 106 | password: "jppassword" 107 | udp: true 108 | my_rule: 109 | type: rule 110 | rule: 111 | - type: domain 112 | method: suffix 113 | domain: google.com 114 | target: jp 115 | - type: domain 116 | method: keyword 117 | domain: twitter 118 | target: us 119 | - type: any 120 | target: local 121 | server: 122 | mixed: 123 | type: http+socks5 124 | bind: 0.0.0.0:10800 125 | net: my_rule 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import { themes as prismThemes } from 'prism-react-renderer'; 2 | import type { Config } from '@docusaurus/types'; 3 | import type * as Preset from '@docusaurus/preset-classic'; 4 | 5 | const config: Config = { 6 | title: 'Rabbit Digger Pro', 7 | tagline: '由 Rust 编写的一体化代理工具', 8 | favicon: 'img/favicon.ico', 9 | 10 | // Set the production url of your site here 11 | url: 'https://rabbit-digger.com', 12 | // Set the // pathname under which your site is served 13 | // For GitHub pages deployment, it is often '//' 14 | baseUrl: '/', 15 | 16 | // GitHub pages deployment config. 17 | // If you aren't using GitHub pages, you don't need these. 18 | organizationName: 'rabbit-digger', // Usually your GitHub org/user name. 19 | projectName: 'rabbit-digger-pro', // Usually your repo name. 20 | 21 | onBrokenLinks: 'throw', 22 | onBrokenMarkdownLinks: 'warn', 23 | 24 | // Even if you don't use internalization, you can use this field to set useful 25 | // metadata like html lang. For example, if your site is Chinese, you may want 26 | // to replace "en" with "zh-Hans". 27 | i18n: { 28 | defaultLocale: 'zh-CN', 29 | locales: ['zh-CN', 'en'], 30 | }, 31 | 32 | presets: [ 33 | [ 34 | 'classic', 35 | { 36 | docs: { 37 | sidebarPath: './sidebars.ts', 38 | // Please change this to your repo. 39 | // Remove this to remove the "edit this page" links. 40 | editUrl: 41 | 'https://github.com/spacemeowx2/rabbit-digger-pro/tree/main/docs/', 42 | }, 43 | theme: { 44 | customCss: './src/css/custom.css', 45 | }, 46 | } satisfies Preset.Options, 47 | ], 48 | ], 49 | 50 | themeConfig: { 51 | // Replace with your project's social card 52 | image: 'img/docusaurus-social-card.jpg', 53 | navbar: { 54 | title: 'Rabbit Digger Pro', 55 | logo: { 56 | alt: 'Rabbit Digger Pro', 57 | src: 'img/logo.svg', 58 | }, 59 | items: [ 60 | { 61 | type: 'docSidebar', 62 | sidebarId: 'tutorialSidebar', 63 | position: 'left', 64 | label: '入门指南', 65 | }, 66 | { 67 | type: 'docSidebar', 68 | sidebarId: 'configSidebar', 69 | position: 'left', 70 | label: '配置指南', 71 | }, 72 | { 73 | href: 'https://github.com/spacemeowx2/rabbit-digger-pro', 74 | label: 'GitHub', 75 | position: 'right', 76 | }, 77 | ], 78 | }, 79 | footer: { 80 | style: 'dark', 81 | copyright: `Copyright © ${new Date().getFullYear()} Rabbit Digger Pro. Built with Docusaurus.`, 82 | }, 83 | prism: { 84 | theme: prismThemes.github, 85 | darkTheme: prismThemes.dracula, 86 | }, 87 | } satisfies Preset.ThemeConfig, 88 | markdown: { 89 | mermaid: true, 90 | }, 91 | themes: ['@docusaurus/theme-mermaid'], 92 | }; 93 | 94 | export default config; 95 | -------------------------------------------------------------------------------- /docs/i18n/en/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Introduction 6 | 7 | `rabbit-digger` is a proxy software written in [Rust](https://www.rust-lang.org/). 8 | 9 | 10 | It is still in the ~~rapid~~ *slowly* development stage. The documentation may not be consistent with actual usage, so please [submit an issue](https://github.com/rabbit-digger/rabbit-digger.github.io/issues/new) if you find any inconsistencies. 11 | 12 | ## Supported Protocol 13 | 14 | * Shadowsocks 15 | * Trojan 16 | * HTTP 17 | * Socks5 18 | * obfs(http_simple) 19 | 20 | ## Supported Server Protocol 21 | 22 | * Socks5 23 | * HTTP 24 | * http+socks on the same port 25 | * Shadowsocks 26 | 27 | 28 | # Installation 29 | 30 | Go to the [Release page](https://github.com/rabbit-digger/rabbit-digger-pro/releases) to download the binary file. 31 | 32 | # Common Usage 33 | 34 | ## Normal mode 35 | 36 | ``` 37 | rabbit-digger-pro -c config.example.yaml 38 | ``` 39 | 40 | ## Normal mode + Control port + Access Token 41 | 42 | ``` 43 | rabbit-digger-pro -c config.example.yaml -b 127.0.0.1:8030 --access-token token 44 | ``` 45 | 46 | ## Control mode, without any config at launch 47 | 48 | ``` 49 | rabbit-digger-pro server -b 127.0.0.1:8030 --access-token token 50 | ``` 51 | 52 | # Command line parameters 53 | 54 | ``` 55 | rabbit-digger-pro 0.1.0 56 | 57 | USAGE: 58 | rabbit-digger-pro [OPTIONS] [SUBCOMMAND] 59 | 60 | FLAGS: 61 | -h, --help Prints help information 62 | -V, --version Prints version information 63 | 64 | OPTIONS: 65 | --access-token Access token [env: RD_ACCESS_TOKEN=] 66 | -b, --bind HTTP endpoint bind address [env: RD_BIND=] 67 | -c, --config Path to config file [env: RD_CONFIG=] [default: config.yaml] 68 | --userdata Userdata [env: RD_USERDATA=] 69 | --web-ui Web UI. Folder path [env: RD_WEB_UI=] 70 | --write-config Write generated config to path 71 | 72 | SUBCOMMANDS: 73 | generate-schema Generate schema to path, if not present, output to stdout 74 | help Prints this message or the help of the given subcommand(s) 75 | server Run in server mode 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rabbit-digger-pro-docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.5.2", 19 | "@docusaurus/preset-classic": "3.5.2", 20 | "@docusaurus/theme-mermaid": "^3.5.2", 21 | "@mdx-js/react": "^3.0.1", 22 | "clsx": "^2.1.1", 23 | "prism-react-renderer": "^2.4.0", 24 | "react": "^18.0.0", 25 | "react-dom": "^18.0.0" 26 | }, 27 | "devDependencies": { 28 | "@docusaurus/module-type-aliases": "3.5.2", 29 | "@docusaurus/tsconfig": "3.5.2", 30 | "@docusaurus/types": "3.5.2", 31 | "typescript": "~5.5.4" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.5%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 3 chrome version", 41 | "last 3 firefox version", 42 | "last 5 safari version" 43 | ] 44 | }, 45 | "engines": { 46 | "node": ">=18.0" 47 | }, 48 | "packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b" 49 | } -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | tutorialSidebar: [{ type: 'autogenerated', dirName: 'tutorial' }], 16 | configSidebar: [{ type: 'autogenerated', dirName: 'config' }], 17 | 18 | // But you can create a sidebar manually 19 | /* 20 | tutorialSidebar: [ 21 | 'intro', 22 | 'hello', 23 | { 24 | type: 'category', 25 | label: 'Tutorial', 26 | items: ['tutorial-basics/create-a-document'], 27 | }, 28 | ], 29 | */ 30 | }; 31 | 32 | export default sidebars; 33 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | // 导入 SVG 图标 6 | import ReloadIcon from '@site/static/img/features/reload.svg'; 7 | import ConfigIcon from '@site/static/img/features/config.svg'; 8 | import SchemaIcon from '@site/static/img/features/schema.svg'; 9 | import ProtocolIcon from '@site/static/img/features/protocol.svg'; 10 | import RuleIcon from '@site/static/img/features/rule.svg'; 11 | import ApiIcon from '@site/static/img/features/api.svg'; 12 | import ClashIcon from '@site/static/img/features/clash.svg'; 13 | import PluginIcon from '@site/static/img/features/plugin.svg'; 14 | import CrossPlatformIcon from '@site/static/img/features/cross-platform.svg'; 15 | 16 | type FeatureItem = { 17 | title: string; 18 | description: JSX.Element; 19 | icon: React.ComponentType>; 20 | }; 21 | 22 | const FeatureList: FeatureItem[] = [ 23 | { 24 | title: '热重加载', 25 | description: ( 26 | <>实时生效的配置更新,无需重启程序即可应用更改 27 | ), 28 | icon: ReloadIcon, 29 | }, 30 | { 31 | title: '灵活配置', 32 | description: ( 33 | <>代理可以随意嵌套组合,完整支持 TCP 和 UDP 转发 34 | ), 35 | icon: ConfigIcon, 36 | }, 37 | { 38 | title: 'JSON Schema 生成', 39 | description: ( 40 | <>无需查文档,通过代码补全直接编写配置文件 41 | ), 42 | icon: SchemaIcon, 43 | }, 44 | { 45 | title: '多协议支持', 46 | description: ( 47 | <>支持 Shadowsocks、Trojan、HTTP、SOCKS5 等多种代理协议 48 | ), 49 | icon: ProtocolIcon, 50 | }, 51 | { 52 | title: '规则路由', 53 | description: ( 54 | <>强大的分流规则系统,支持域名、IP、GeoIP 等多种匹配方式 55 | ), 56 | icon: RuleIcon, 57 | }, 58 | { 59 | title: 'API 控制', 60 | description: ( 61 | <>提供 HTTP API 接口,支持程序化控制和状态监控 62 | ), 63 | icon: ApiIcon, 64 | }, 65 | { 66 | title: 'Clash 订阅', 67 | description: ( 68 | <>兼容 Clash 配置格式,可直接导入现有的 Clash 配置 69 | ), 70 | icon: ClashIcon, 71 | }, 72 | { 73 | title: '插件系统', 74 | description: ( 75 | <>可扩展的插件架构,支持自定义协议和功能 76 | ), 77 | icon: PluginIcon, 78 | }, 79 | { 80 | title: '跨平台支持', 81 | description: ( 82 | <>支持 Windows、Linux、macOS 等主流操作系统 83 | ), 84 | icon: CrossPlatformIcon, 85 | }, 86 | ]; 87 | 88 | function Feature({ title, description, icon: Icon }: FeatureItem) { 89 | return ( 90 |
91 |
92 | 93 |

{title}

94 |

{description}

95 |
96 |
97 | ); 98 | } 99 | 100 | export default function HomepageFeatures(): JSX.Element { 101 | return ( 102 |
103 |
104 |
105 | {FeatureList.map((props, idx) => ( 106 | 107 | ))} 108 |
109 |
110 |
111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 60px; 10 | width: 60px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 5 | import Layout from '@theme/Layout'; 6 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 7 | 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const { siteConfig } = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 |

{siteConfig.title}

16 |

{siteConfig.tagline}

17 |
18 | 21 | 快速上手 22 | 23 |
24 |
25 |
26 | ); 27 | } 28 | 29 | export default function Home(): JSX.Element { 30 | const { siteConfig } = useDocusaurusContext(); 31 | return ( 32 | 33 | 34 |
35 | 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacemeowx2/rabbit-digger-pro/b212e32730c6fedfc1c74efac3eb7a4e6491f6cc/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacemeowx2/rabbit-digger-pro/b212e32730c6fedfc1c74efac3eb7a4e6491f6cc/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacemeowx2/rabbit-digger-pro/b212e32730c6fedfc1c74efac3eb7a4e6491f6cc/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacemeowx2/rabbit-digger-pro/b212e32730c6fedfc1c74efac3eb7a4e6491f6cc/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/features/api.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/static/img/features/clash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/static/img/features/config.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/features/cross-platform.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /docs/static/img/features/plugin.svg: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /docs/static/img/features/protocol.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/static/img/features/reload.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/static/img/features/rule.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/static/img/features/schema.svg: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } -------------------------------------------------------------------------------- /docs_old/telemetry.md: -------------------------------------------------------------------------------- 1 | ## Start jaeger 2 | 3 | ``` 4 | docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 jaegertracing/all-in-one:1.26 5 | ``` 6 | 7 | ## Run with telemetry feature 8 | 9 | ``` 10 | cargo run --features raw,telemetry 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/gateway.yaml: -------------------------------------------------------------------------------- 1 | # raw net can be a gateway of a Switch 2 | 3 | net: 4 | gateway: 5 | type: raw 6 | device: eth0 7 | mtu: 1400 8 | ip_addr: 10.13.37.1/16 9 | # must be the same with your adapter if you are using wireless adapter 10 | ethernet_addr: 70:85:C2:71:43:1D 11 | # you can change net to any protocol that support UDP. 12 | net: local 13 | # any device in `ip_addr` subnet can use net above as a real net. 14 | forward: true 15 | server: 16 | # a echo server to keep rabbit-digger-pro running 17 | echo: 18 | type: echo 19 | bind: 0.0.0.0:12345 20 | net: gateway 21 | -------------------------------------------------------------------------------- /examples/geoip.yaml: -------------------------------------------------------------------------------- 1 | # This rule only allows connection to CN. 2 | net: 3 | rule_net: 4 | type: rule 5 | rule: 6 | - type: geoip 7 | country: CN 8 | target: local 9 | - type: any 10 | target: noop 11 | server: 12 | mixed: 13 | type: http+socks5 14 | bind: 127.0.0.1:10800 15 | net: rule_net 16 | -------------------------------------------------------------------------------- /examples/http_auth_server.yaml: -------------------------------------------------------------------------------- 1 | # HTTP 代理服务器配置示例 - 带用户名密码认证 2 | net: 3 | direct: 4 | type: local 5 | server: 6 | http: 7 | type: http 8 | listen: direct 9 | net: direct 10 | bind: "127.0.0.1:8080" 11 | # 添加认证信息 12 | auth: 13 | username: "user" 14 | password: "pass" 15 | -------------------------------------------------------------------------------- /examples/nft-tproxy/reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # set -x 3 | 4 | RD_FW_MARK="${RD_FW_MARK:=0xfe}" 5 | RD_TABLE="${RD_TABLE:=101}" 6 | RD_DISABLE_IPV6="${RD_DISABLE_IPV6:=0}" 7 | 8 | if [ "$(id -u)" != "0" ]; then 9 | echo "This script must be run as root" 1>&2 10 | exit 1 11 | fi 12 | 13 | nft delete table inet rabbit_digger 14 | 15 | ip -4 rule del fwmark $RD_FW_MARK table $RD_TABLE 16 | ip -4 route del local 0/0 dev lo table $RD_TABLE 17 | -------------------------------------------------------------------------------- /examples/nft-tproxy/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # set -xe 3 | 4 | RD_MARK="${RD_MARK:=0x11}" 5 | RD_PORT="${RD_PORT:=19810}" 6 | RD_PORT6="${RD_PORT6:=19811}" 7 | RD_TABLE="${RD_TABLE:=101}" 8 | RD_FW_MARK="${RD_FW_MARK:=0xfe}" 9 | RD_CT_MARK="${RD_CT_MARK:=0x10}" 10 | RD_INTERNAL_DEV="${RD_INTERNAL_DEV:=br-lan}" 11 | RD_DISABLE_IPV6="${RD_DISABLE_IPV6:=0}" 12 | RD_ENABLE_SELF="${RD_ENABLE_SELF:=0}" 13 | RD_EXCLUDE_IP="$RD_EXCLUDE_IP" 14 | RD_EXCLUDE_MAC="$RD_EXCLUDE_MAC" 15 | RD_HIJACK_DNS="${RD_HIJACK_DNS:=1}" 16 | 17 | if [ "$(id -u)" != "0" ]; then 18 | echo "This script must be run as root" 1>&2 19 | exit 1 20 | fi 21 | 22 | # Strategy Route 23 | ip -4 route add local 0/0 dev lo table $RD_TABLE 24 | ip -4 rule add fwmark $RD_FW_MARK table $RD_TABLE 25 | 26 | nft "add table inet rabbit_digger" 27 | nft "add chain inet rabbit_digger output { type filter hook output priority raw; policy accept; }" 28 | nft "add chain inet rabbit_digger prerouting { type filter hook prerouting priority mangle; policy accept; }" 29 | nft "add set inet rabbit_digger localnetwork { type ipv4_addr; flags interval; auto-merge; }" 30 | nft "add element inet rabbit_digger localnetwork { 0.0.0.0/8, 127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 192.168.0.0/16, 224.0.0.0/4, 240.0.0.0/4, 172.16.0.0/12}" 31 | 32 | # if RD_INTERNAL_DEV is existed 33 | if [ -d /sys/class/net/$RD_INTERNAL_DEV ]; then 34 | nft "add rule inet rabbit_digger prerouting iifname != $RD_INTERNAL_DEV counter return" 35 | fi 36 | nft "add rule inet rabbit_digger prerouting meta mark $RD_MARK counter return" 37 | nft "add rule inet rabbit_digger prerouting ip daddr @localnetwork return" 38 | nft "add rule inet rabbit_digger prerouting meta l4proto { udp } tproxy ip to :$RD_PORT mark set $RD_FW_MARK counter accept" 39 | nft "add rule inet rabbit_digger prerouting meta l4proto { tcp } tproxy ip to :$RD_PORT mark set $RD_FW_MARK counter accept" 40 | -------------------------------------------------------------------------------- /examples/raw.yaml: -------------------------------------------------------------------------------- 1 | net: 2 | raw: 3 | type: raw 4 | device: "eth0" 5 | mtu: 1400 6 | # must be the same with your adapter if you are using wireless adapter 7 | ethernet_addr: 70:85:C2:71:43:1D 8 | # pick a free ip in your subnet 9 | ip_addr: 192.168.233.4/24 10 | # your gateway 11 | gateway: 192.168.233.1 12 | # DNS resolving is not implemented in `raw` yet. So use local domain resolver here. 13 | resolve_raw: 14 | type: resolve 15 | net: raw 16 | resolve_net: local 17 | ipv6: false 18 | server: 19 | mixed: 20 | # could also be `http` or `socks5`. This `type` supports both `http` and `socks5`. 21 | type: http+socks5 22 | # the proxy server is now on 11221. Then you can use smoltcp to browser the Internet by this proxy. 23 | bind: 127.0.0.1:11221 24 | net: resolve_raw 25 | -------------------------------------------------------------------------------- /examples/socks5_server.yaml: -------------------------------------------------------------------------------- 1 | # A simple socks5 server 2 | server: 3 | socks5: 4 | type: socks5 5 | bind: 0.0.0.0:1080 6 | -------------------------------------------------------------------------------- /examples/tproxy/reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # set -x 3 | 4 | RD_FW_MARK="${RD_FW_MARK:=0xfe}" 5 | RD_TABLE="${RD_TABLE:=101}" 6 | RD_DISABLE_IPV6="${RD_DISABLE_IPV6:=0}" 7 | 8 | if [ "$(id -u)" != "0" ]; then 9 | echo "This script must be run as root" 1>&2 10 | exit 1 11 | fi 12 | 13 | if [ "$RD_DISABLE_IPV6" != "1" ]; then 14 | ip6tables -t mangle -D OUTPUT -j RD_OUTPUT 15 | ip6tables -t mangle -D PREROUTING -j RD_PREROUTING 16 | 17 | ip6tables -t mangle -F RD_PREROUTING 18 | ip6tables -t mangle -X RD_PREROUTING 19 | 20 | ip6tables -t mangle -F RD_OUTPUT 21 | ip6tables -t mangle -X RD_OUTPUT 22 | 23 | ip -6 rule del fwmark $RD_FW_MARK table $RD_TABLE 24 | ip -6 route del local ::/0 dev lo table $RD_TABLE 25 | fi 26 | 27 | iptables -t mangle -D OUTPUT -j RD_OUTPUT 28 | iptables -t mangle -D PREROUTING -j RD_PREROUTING 29 | 30 | iptables -t mangle -F RD_PREROUTING 31 | iptables -t mangle -X RD_PREROUTING 32 | 33 | iptables -t mangle -F RD_OUTPUT 34 | iptables -t mangle -X RD_OUTPUT 35 | 36 | ip -4 rule del fwmark $RD_FW_MARK table $RD_TABLE 37 | ip -4 route del local 0/0 dev lo table $RD_TABLE 38 | -------------------------------------------------------------------------------- /examples/tproxy/tproxy.yaml: -------------------------------------------------------------------------------- 1 | # This config is a sample of how to use the tproxy module. 2 | # Make sure setup.sh is run before you run this config. 3 | # Root is required to use `mark` option in `local` net. 4 | 5 | net: 6 | local: 7 | type: local 8 | mark: 254 9 | socks5: 10 | type: socks5 11 | server: 127.0.0.1:10800 12 | 13 | server: 14 | tproxy4: 15 | type: tproxy 16 | bind: 0.0.0.0:19810 17 | net: socks5 18 | tproxy6: 19 | type: tproxy 20 | bind: 0.0.0.0:19811 21 | net: socks5 22 | -------------------------------------------------------------------------------- /examples/tracing/config/grafana-datasources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: "Tempo" 5 | type: tempo 6 | access: proxy 7 | orgId: 1 8 | url: http://tempo:3200 9 | basicAuth: false 10 | isDefault: true 11 | version: 1 12 | editable: false 13 | apiVersion: 1 14 | uid: tempo-query 15 | - name: Loki 16 | type: loki 17 | access: proxy 18 | orgId: 1 19 | url: http://loki:3100 20 | basicAuth: false 21 | isDefault: false 22 | version: 1 23 | editable: false 24 | apiVersion: 1 25 | jsonData: 26 | derivedFields: 27 | - datasourceUid: Tempo 28 | matcherRegex: \"trace_id\":"(\w+)" 29 | name: TraceID 30 | url: $${__value.raw} 31 | -------------------------------------------------------------------------------- /examples/tracing/config/grafana.ini: -------------------------------------------------------------------------------- 1 | [feature_toggles] 2 | enable = tempoSearch tempoBackendSearch 3 | -------------------------------------------------------------------------------- /examples/tracing/config/overrides.yaml: -------------------------------------------------------------------------------- 1 | overrides: 2 | "single-tenant": 3 | search_tags_allow_list: 4 | - "instance" 5 | ingestion_rate_strategy: "local" 6 | ingestion_rate_limit_bytes: 15000000 7 | ingestion_burst_size_bytes: 20000000 8 | max_traces_per_user: 10000 9 | max_global_traces_per_user: 0 10 | max_bytes_per_trace: 5000000 11 | max_search_bytes_per_trace: 0 12 | max_bytes_per_tag_values_query: 5000000 13 | block_retention: 0s 14 | -------------------------------------------------------------------------------- /examples/tracing/config/prometheus.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interval: 15s 4 | 5 | scrape_configs: 6 | - job_name: "prometheus" 7 | static_configs: 8 | - targets: ["localhost:9090"] 9 | - job_name: "tempo" 10 | static_configs: 11 | - targets: ["tempo:3200"] 12 | -------------------------------------------------------------------------------- /examples/tracing/config/tempo.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | http_listen_port: 3200 3 | 4 | distributor: 5 | search_tags_deny_list: 6 | - "instance" 7 | - "version" 8 | receivers: # this configuration will listen on all ports and protocols that tempo is capable of. 9 | jaeger: # the receives all come from the OpenTelemetry collector. more configuration information can 10 | protocols: # be found there: https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver 11 | thrift_http: # 12 | grpc: # for a production deployment you should only enable the receivers you need! 13 | thrift_binary: 14 | thrift_compact: 15 | zipkin: 16 | otlp: 17 | protocols: 18 | http: 19 | grpc: 20 | opencensus: 21 | 22 | ingester: 23 | trace_idle_period: 10s # the length of time after a trace has not received spans to consider it complete and flush it 24 | max_block_bytes: 1_000_000 # cut the head block when it hits this size or ... 25 | max_block_duration: 5m # this much time passes 26 | 27 | compactor: 28 | compaction: 29 | compaction_window: 1h # blocks in this time window will be compacted together 30 | max_block_bytes: 100_000_000 # maximum size of compacted blocks 31 | block_retention: 1h 32 | compacted_block_retention: 10m 33 | 34 | storage: 35 | trace: 36 | backend: local # backend configuration to use 37 | block: 38 | bloom_filter_false_positive: .05 # bloom filter false positive rate. lower values create larger filters but fewer false positives 39 | index_downsample_bytes: 1000 # number of bytes per index record 40 | encoding: zstd # block encoding/compression. options: none, gzip, lz4-64k, lz4-256k, lz4-1M, lz4, snappy, zstd, s2 41 | wal: 42 | path: /tmp/tempo/wal # where to store the the wal locally 43 | encoding: snappy # wal encoding/compression. options: none, gzip, lz4-64k, lz4-256k, lz4-1M, lz4, snappy, zstd, s2 44 | local: 45 | path: /tmp/tempo/blocks 46 | pool: 47 | max_workers: 100 # worker pool determines the number of parallel requests to the object store backend 48 | queue_depth: 10000 49 | 50 | overrides: 51 | per_tenant_override_config: /etc/overrides.yaml 52 | -------------------------------------------------------------------------------- /examples/tracing/config/vector.toml: -------------------------------------------------------------------------------- 1 | [sources.all] 2 | type = "socket" 3 | address = "0.0.0.0:9000" 4 | mode = "tcp" 5 | decoding.codec = "json" 6 | 7 | [transforms.logs-input] 8 | type = "filter" 9 | inputs = ["all"] 10 | condition = "!exists(.full)" 11 | 12 | [transforms.traffic-input] 13 | type = "filter" 14 | inputs = ["all"] 15 | condition = "exists(.full)" 16 | 17 | [transforms.logs] 18 | type = "remap" 19 | inputs = ["logs-input"] 20 | source = """ 21 | del(.source_type) 22 | .type = "logs" 23 | 24 | if exists(.fields.message) { 25 | .message = .fields.message 26 | del(.fields.message) 27 | } 28 | 29 | if exists(.fields.ctx) { 30 | .ctx = parse_json!(.fields.ctx) 31 | del(.fields.ctx) 32 | 33 | if exists(.ctx.src_socket_addr) { 34 | .ctx.src_socket_addr = parse_regex!(.ctx.src_socket_addr, r'(?P.*?):(?P\\d+)$') 35 | } 36 | if exists(.ctx.dest_socket_addr) { 37 | .ctx.dest_socket_addr = parse_regex!(.ctx.dest_socket_addr, r'(?P.*?):(?P\\d+)$') 38 | } 39 | if exists(.ctx.dest_domain) { 40 | .ctx.dest_domain = parse_regex!(.ctx.dest_domain, r'(?P.*?):(?P\\d+)$') 41 | } 42 | } 43 | """ 44 | 45 | [transforms.traffic] 46 | type = "remap" 47 | inputs = ["traffic-input"] 48 | source = """ 49 | del(.source_type) 50 | .type = "traffic" 51 | .total_download = .full.total_download 52 | .total_upload = .full.total_upload 53 | .connection_count = length!(.full.connections) 54 | del(.full) 55 | """ 56 | 57 | [sinks.loki] 58 | type = "loki" 59 | inputs = ["logs", "traffic"] 60 | endpoint = "http://loki:3100" 61 | encoding.codec = "json" 62 | labels.type = "{{ type }}" 63 | labels.ctx_src_host = "{{ ctx.src_socket_addr }}" 64 | -------------------------------------------------------------------------------- /examples/tracing/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | volumes: 4 | tempo-data: 5 | driver: local 6 | loki-data: 7 | driver: local 8 | grafana-data: 9 | driver: local 10 | services: 11 | 12 | tempo: 13 | image: grafana/tempo:latest 14 | command: [ "-search.enabled=true", "-config.file=/etc/tempo.yaml" ] 15 | volumes: 16 | - ./config/tempo.yaml:/etc/tempo.yaml 17 | - ./config/overrides.yaml:/etc/overrides.yaml 18 | - tempo-data/:/tmp/tempo 19 | ports: 20 | - "3200:3200" # tempo 21 | - "14268:14268" # jaeger ingest 22 | 23 | loki: 24 | image: grafana/loki:2.5.0 25 | command: [ "-config.file=/etc/loki/local-config.yaml" ] 26 | volumes: 27 | - loki-data:/loki 28 | ports: 29 | - "3100:3100" # loki needs to be exposed so it receives logs 30 | environment: 31 | - JAEGER_AGENT_HOST=tempo 32 | - JAEGER_ENDPOINT=http://tempo:14268/api/traces # send traces to Tempo 33 | - JAEGER_SAMPLER_TYPE=const 34 | - JAEGER_SAMPLER_PARAM=1 35 | 36 | grafana: 37 | image: grafana/grafana:main # track main as search is under active development 38 | volumes: 39 | - ./config/grafana.ini:/etc/grafana/grafana.ini 40 | - ./config/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml 41 | - grafana-data:/var/lib/grafana 42 | environment: 43 | - GF_AUTH_ANONYMOUS_ENABLED=true 44 | - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin 45 | - GF_AUTH_DISABLE_LOGIN_FORM=true 46 | ports: 47 | - "3000:3000" 48 | 49 | vector: 50 | image: timberio/vector:0.21.2-alpine 51 | volumes: 52 | - ./config/vector.toml:/etc/vector/vector.toml 53 | ports: 54 | - "9000" 55 | depends_on: 56 | - loki 57 | 58 | websocat-logs: 59 | image: mtilson/websocat:latest 60 | entrypoint: sh -c "websocat ws://192.168.233.1:8030/api/stream/logs | nc vector 9000" 61 | depends_on: 62 | - vector 63 | restart: unless-stopped 64 | 65 | websocat-traffic: 66 | image: mtilson/websocat:latest 67 | entrypoint: sh -c "websocat -B 1048576 ws://192.168.233.1:8030/api/stream/connection | nc vector 9000" 68 | depends_on: 69 | - vector 70 | restart: unless-stopped 71 | -------------------------------------------------------------------------------- /ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rdp" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | description = "FFI wrapper for rabbit-digger-pro" 7 | license = "MIT OR Apache-2.0" 8 | 9 | [dependencies] 10 | anyhow = "1.0.38" 11 | rabbit-digger-pro = { path = "../", default-features = false, features = [ 12 | "trojan", 13 | "rpc", 14 | "obfs", 15 | "api_server", 16 | ] } 17 | tokio = { version = "1.15.0", features = ["full"] } 18 | tokio-stream = { version = "0.1.6", features = ["net", "sync", "time"] } 19 | tracing-subscriber = { version = "0.3.7", features = [ 20 | "registry", 21 | "env-filter", 22 | "json", 23 | ] } 24 | tracing = "0.1.26" 25 | 26 | [build-dependencies] 27 | cbindgen = "0.20.0" 28 | 29 | [lib] 30 | crate-type = ["cdylib", "staticlib"] 31 | -------------------------------------------------------------------------------- /ffi/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cbindgen; 2 | 3 | use std::env; 4 | 5 | use cbindgen::Language; 6 | 7 | fn main() { 8 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 9 | 10 | cbindgen::Builder::new() 11 | .with_crate(crate_dir) 12 | .with_language(Language::C) 13 | .generate() 14 | .expect("Unable to generate bindings") 15 | .write_to_file("rdp.h"); 16 | } 17 | -------------------------------------------------------------------------------- /ffi/rdp.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct RdpRuntime RdpRuntime; 7 | 8 | typedef int32_t RESULT; 9 | 10 | typedef struct RdpRuntime *RDP; 11 | 12 | /** 13 | * No error. 14 | */ 15 | #define RESULT_OK 0 16 | 17 | /** 18 | * Unknown error. 19 | */ 20 | #define RESULT_ERR_UNKNOWN -1 21 | 22 | /** 23 | * Utf8 error. 24 | */ 25 | #define RESULT_ERR_UTF8 -2 26 | 27 | /** 28 | * The other side is closed. 29 | */ 30 | #define RESULT_ERR_CLOSED -3 31 | 32 | void rdp_setup_stdout_logger(void); 33 | 34 | RESULT rdp_run(RDP *rabbit_digger, const char *config); 35 | 36 | RESULT rdp_update_config(RDP rabbit_digger, const char *config); 37 | 38 | RESULT rdp_stop(RDP *rabbit_digger); 39 | -------------------------------------------------------------------------------- /gen_coverage.sh: -------------------------------------------------------------------------------- 1 | # Make sure you have cargo-tarpaulin installed: `cargo install cargo-tarpaulin` 2 | 3 | cargo tarpaulin --target-dir ./coverage/target --workspace --out Lcov --output-dir coverage --skip-clean 4 | -------------------------------------------------------------------------------- /protocol/obfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obfs" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rd-interface = { path = "../../rd-interface/", version = "0.4" } 9 | rd-std = { path = "../../rd-std/", version = "0.1" } 10 | serde = "1.0.177" 11 | tokio = "1.29.1" 12 | pin-project-lite = "0.2.10" 13 | futures = "0.3.28" 14 | rand = "0.8.5" 15 | base64 = "0.21.2" 16 | -------------------------------------------------------------------------------- /protocol/obfs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use obfs_net::{ObfsNet, ObfsNetConfig}; 4 | use rd_interface::{ 5 | prelude::*, registry::Builder, Address, Context, Net, Registry, Result, TcpStream, 6 | }; 7 | 8 | mod http_simple; 9 | mod obfs_net; 10 | mod plain; 11 | 12 | impl Builder for ObfsNet { 13 | const NAME: &'static str = "obfs"; 14 | type Config = ObfsNetConfig; 15 | type Item = ObfsNet; 16 | 17 | fn build(config: Self::Config) -> Result { 18 | ObfsNet::new(config) 19 | } 20 | } 21 | 22 | pub fn init(registry: &mut Registry) -> Result<()> { 23 | registry.add_net::(); 24 | 25 | Ok(()) 26 | } 27 | 28 | /// An obfs protocol used in front of a `TcpStream` 29 | pub trait Obfs { 30 | /// Wrap the `TcpStream` with obfs request and response. Used by client. 31 | fn tcp_connect(&self, tcp: TcpStream, ctx: &mut Context, addr: &Address) -> Result; 32 | /// Wrap the `TcpStream` with obfs request and response. Used by server. 33 | fn tcp_accept(&self, tcp: TcpStream, addr: SocketAddr) -> Result; 34 | } 35 | 36 | #[rd_config] 37 | #[derive(Debug)] 38 | #[serde(rename_all = "snake_case")] 39 | pub enum ObfsType { 40 | Http(http_simple::HttpSimple), 41 | Plain(plain::Plain), 42 | } 43 | 44 | impl Obfs for ObfsType { 45 | fn tcp_connect(&self, tcp: TcpStream, ctx: &mut Context, addr: &Address) -> Result { 46 | match self { 47 | ObfsType::Http(i) => i.tcp_connect(tcp, ctx, addr), 48 | ObfsType::Plain(i) => i.tcp_connect(tcp, ctx, addr), 49 | } 50 | } 51 | 52 | fn tcp_accept(&self, tcp: TcpStream, addr: SocketAddr) -> Result { 53 | match self { 54 | ObfsType::Http(i) => i.tcp_accept(tcp, addr), 55 | ObfsType::Plain(i) => i.tcp_accept(tcp, addr), 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /protocol/obfs/src/obfs_net.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use crate::{Obfs, ObfsType}; 4 | use rd_interface::{ 5 | async_trait, prelude::*, registry::NetRef, Address, Arc, Context, INet, ITcpListener, IntoDyn, 6 | Net, Result, TcpListener, TcpStream, 7 | }; 8 | 9 | type BoxObfs = Arc; 10 | 11 | #[rd_config] 12 | #[derive(Debug)] 13 | pub struct ObfsNetConfig { 14 | #[serde(default)] 15 | pub net: NetRef, 16 | #[serde(flatten)] 17 | pub obfs_type: ObfsType, 18 | } 19 | 20 | pub struct ObfsNet { 21 | net: Net, 22 | obfs: Arc, 23 | } 24 | 25 | impl ObfsNet { 26 | pub fn new(config: ObfsNetConfig) -> Result { 27 | Ok(ObfsNet { 28 | net: config.net.value_cloned(), 29 | obfs: Arc::new(config.obfs_type), 30 | }) 31 | } 32 | } 33 | 34 | #[async_trait] 35 | impl rd_interface::TcpConnect for ObfsNet { 36 | async fn tcp_connect(&self, ctx: &mut Context, addr: &Address) -> Result { 37 | let tcp = self.net.tcp_connect(ctx, addr).await?; 38 | Ok(self.obfs.tcp_connect(tcp, ctx, addr)?) 39 | } 40 | } 41 | 42 | #[async_trait] 43 | impl rd_interface::TcpBind for ObfsNet { 44 | async fn tcp_bind(&self, ctx: &mut Context, addr: &Address) -> Result { 45 | let listener = self.net.tcp_bind(ctx, addr).await?; 46 | Ok(ObfsTcpListener(listener, self.obfs.clone()).into_dyn()) 47 | } 48 | } 49 | 50 | impl INet for ObfsNet { 51 | fn provide_tcp_connect(&self) -> Option<&dyn rd_interface::TcpConnect> { 52 | Some(self) 53 | } 54 | 55 | fn provide_tcp_bind(&self) -> Option<&dyn rd_interface::TcpBind> { 56 | Some(self) 57 | } 58 | } 59 | 60 | struct ObfsTcpListener(TcpListener, BoxObfs); 61 | 62 | #[async_trait] 63 | impl ITcpListener for ObfsTcpListener { 64 | async fn accept(&self) -> Result<(TcpStream, SocketAddr)> { 65 | let (tcp, addr) = self.0.accept().await?; 66 | Ok((self.1.tcp_accept(tcp, addr)?, addr)) 67 | } 68 | 69 | async fn local_addr(&self) -> Result { 70 | self.0.local_addr().await 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use rd_std::tests::{assert_net_provider, ProviderCapability, TestNet}; 77 | 78 | use super::*; 79 | 80 | #[test] 81 | fn test_provider() { 82 | let net = TestNet::new().into_dyn(); 83 | 84 | let obfs = ObfsNet::new(ObfsNetConfig { 85 | net: NetRef::new_with_value("test".into(), net.clone()), 86 | obfs_type: ObfsType::Plain(Default::default()), 87 | }) 88 | .unwrap() 89 | .into_dyn(); 90 | 91 | assert_net_provider( 92 | &obfs, 93 | ProviderCapability { 94 | tcp_connect: true, 95 | tcp_bind: true, 96 | ..Default::default() 97 | }, 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /protocol/obfs/src/plain.rs: -------------------------------------------------------------------------------- 1 | use crate::Obfs; 2 | use rd_interface::{prelude::*, Address, Result, TcpStream}; 3 | 4 | #[rd_config] 5 | #[derive(Debug, Default)] 6 | pub struct Plain; 7 | 8 | impl Obfs for Plain { 9 | fn tcp_connect( 10 | &self, 11 | tcp: TcpStream, 12 | _ctx: &mut rd_interface::Context, 13 | _addr: &Address, 14 | ) -> Result { 15 | Ok(tcp) 16 | } 17 | 18 | fn tcp_accept(&self, tcp: TcpStream, _addr: std::net::SocketAddr) -> Result { 19 | Ok(tcp) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /protocol/raw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raw" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rd-interface = { path = "../../rd-interface/", version = "0.4" } 9 | rd-std = { path = "../../rd-std/", version = "0.1" } 10 | # rd-interface = "0.3" 11 | serde = "1.0" 12 | tracing = "0.1.26" 13 | tokio-smoltcp = "0.2.4" 14 | tokio = { version = "1.5.0", features = ["rt", "macros", "net"] } 15 | pcap = { version = "0.9.1", optional = true } 16 | futures = "0.3" 17 | lru_time_cache = "0.11" 18 | parking_lot = "0.12.1" 19 | parking_lot_core = "0.9.8" 20 | 21 | tokio-util = { version = "0.6", features = ["codec"] } 22 | 23 | [target.'cfg(unix)'.dependencies.tun-crate] 24 | version = "0.5.4" 25 | package = "tun" 26 | features = ["async"] 27 | 28 | [target.'cfg(windows)'.dependencies] 29 | libc = "0.2" 30 | wintun = "0.2" 31 | once_cell = "1.7.2" 32 | # the same as parking_lot_core, should reduce the compile time 33 | windows-sys = { version = "0.48.0", features = [ 34 | "Win32_NetworkManagement_IpHelper", 35 | "Win32_NetworkManagement_Ndis", 36 | "Win32_Foundation", 37 | ] } 38 | 39 | [target.'cfg(unix)'.dependencies] 40 | nix = "0.26.2" 41 | 42 | [features] 43 | default = [] 44 | libpcap = ["pcap"] 45 | -------------------------------------------------------------------------------- /protocol/raw/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use rd_interface::{config::Config, prelude::*}; 4 | use tokio_smoltcp::smoltcp::{phy::Medium, wire::IpCidr}; 5 | 6 | #[rd_config] 7 | #[serde(rename_all = "lowercase")] 8 | #[derive(Copy, Clone)] 9 | pub enum TunTap { 10 | Tap, 11 | Tun, 12 | } 13 | 14 | #[rd_config] 15 | pub struct TunTapConfig { 16 | #[serde(rename = "type")] 17 | pub tuntap: TunTap, 18 | pub name: Option, 19 | /// host address 20 | pub host_addr: String, 21 | } 22 | 23 | #[rd_config] 24 | #[serde(untagged)] 25 | pub enum MaybeString 26 | where 27 | T: Config, 28 | { 29 | #[cfg(feature = "libpcap")] 30 | String(String), 31 | Other(T), 32 | } 33 | 34 | pub type DeviceConfig = MaybeString; 35 | 36 | #[rd_config] 37 | #[derive(Clone, Copy)] 38 | pub enum Layer { 39 | L2, 40 | L3, 41 | } 42 | 43 | impl Default for Layer { 44 | fn default() -> Self { 45 | Layer::L2 46 | } 47 | } 48 | 49 | #[cfg(unix)] 50 | impl From for tun_crate::Layer { 51 | fn from(l: Layer) -> Self { 52 | match l { 53 | Layer::L2 => tun_crate::Layer::L2, 54 | Layer::L3 => tun_crate::Layer::L3, 55 | } 56 | } 57 | } 58 | 59 | impl From for Layer { 60 | fn from(t: TunTap) -> Self { 61 | match t { 62 | TunTap::Tap => Layer::L2, 63 | TunTap::Tun => Layer::L3, 64 | } 65 | } 66 | } 67 | 68 | impl From for Layer { 69 | fn from(m: Medium) -> Self { 70 | match m { 71 | Medium::Ethernet => Layer::L2, 72 | Medium::Ip => Layer::L3, 73 | #[allow(unreachable_patterns)] 74 | _ => panic!("unsupported medium"), 75 | } 76 | } 77 | } 78 | 79 | impl From for Medium { 80 | fn from(l: Layer) -> Self { 81 | match l { 82 | Layer::L2 => Medium::Ethernet, 83 | Layer::L3 => Medium::Ip, 84 | } 85 | } 86 | } 87 | 88 | #[rd_config] 89 | pub struct RawNetConfig { 90 | pub device: DeviceConfig, 91 | pub gateway: Option, 92 | 93 | /// IP Cidr 94 | pub ip_addr: String, 95 | pub ethernet_addr: Option, 96 | pub mtu: usize, 97 | 98 | #[serde(default)] 99 | pub forward: bool, 100 | } 101 | 102 | pub struct TunTapSetup { 103 | pub name: Option, 104 | pub addr: Ipv4Addr, 105 | pub destination_addr: IpCidr, 106 | pub mtu: usize, 107 | pub layer: Layer, 108 | } 109 | -------------------------------------------------------------------------------- /protocol/raw/src/device/boxed.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures::{Sink, SinkExt, Stream, StreamExt}; 8 | use tokio_smoltcp::device::{AsyncDevice, Packet}; 9 | 10 | pub struct BoxedAsyncDevice(pub(crate) Box); 11 | 12 | impl AsyncDevice for BoxedAsyncDevice { 13 | fn capabilities(&self) -> &tokio_smoltcp::device::DeviceCapabilities { 14 | self.0.capabilities() 15 | } 16 | } 17 | 18 | impl Stream for BoxedAsyncDevice { 19 | type Item = io::Result; 20 | 21 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 22 | self.0.poll_next_unpin(cx) 23 | } 24 | } 25 | 26 | impl Sink for BoxedAsyncDevice { 27 | type Error = io::Error; 28 | 29 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 30 | self.0.poll_ready_unpin(cx) 31 | } 32 | 33 | fn start_send(mut self: Pin<&mut Self>, item: Packet) -> Result<(), Self::Error> { 34 | self.0.start_send_unpin(item) 35 | } 36 | 37 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 38 | self.0.poll_flush_unpin(cx) 39 | } 40 | 41 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 42 | self.0.poll_close_unpin(cx) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /protocol/raw/src/device/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::{IpAddr, Ipv4Addr, Ipv6Addr}, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use crate::config::TunTapSetup; 9 | use futures::{ready, Sink, SinkExt, Stream, StreamExt}; 10 | use rd_interface::{error::map_other, Result}; 11 | use tokio_smoltcp::{ 12 | device::{AsyncDevice, DeviceCapabilities, Packet}, 13 | smoltcp::{phy::Checksum, wire::IpAddress}, 14 | }; 15 | use tokio_util::codec::Framed; 16 | use tun_crate::{create_as_async, Configuration, Device, TunPacket, TunPacketCodec}; 17 | 18 | pub struct TunAsyncDevice { 19 | device_name: String, 20 | dev: Framed, 21 | caps: DeviceCapabilities, 22 | } 23 | 24 | pub fn get_tun(cfg: TunTapSetup) -> Result { 25 | let mut config = Configuration::default(); 26 | let netmask = !0u32 >> (32 - cfg.destination_addr.prefix_len()); 27 | 28 | config 29 | .address(IpAddr::from(cfg.addr)) 30 | .destination(match cfg.destination_addr.address() { 31 | IpAddress::Ipv4(v4) => IpAddr::from(Ipv4Addr::from(v4)), 32 | IpAddress::Ipv6(v6) => IpAddr::from(Ipv6Addr::from(v6)), 33 | _ => unreachable!(), 34 | }) 35 | .netmask(netmask) 36 | .layer(cfg.layer.into()) 37 | .up(); 38 | 39 | if let Some(name) = &cfg.name { 40 | config.name(name); 41 | } 42 | 43 | let dev = create_as_async(&config).map_err(map_other)?; 44 | let device_name = dev.get_ref().name().to_string(); 45 | let dev = dev.into_framed(); 46 | 47 | tracing::info!("tun created: {}", device_name); 48 | 49 | let mut caps = DeviceCapabilities::default(); 50 | caps.medium = cfg.layer.into(); 51 | caps.max_transmission_unit = cfg.mtu; 52 | caps.checksum.ipv4 = Checksum::Tx; 53 | caps.checksum.tcp = Checksum::Tx; 54 | caps.checksum.udp = Checksum::Tx; 55 | caps.checksum.icmpv4 = Checksum::Tx; 56 | caps.checksum.icmpv6 = Checksum::Tx; 57 | 58 | Ok(TunAsyncDevice { 59 | device_name, 60 | dev, 61 | caps, 62 | }) 63 | } 64 | 65 | impl Stream for TunAsyncDevice { 66 | type Item = io::Result; 67 | 68 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 69 | let p = ready!(self.dev.poll_next_unpin(cx)); 70 | 71 | Poll::Ready(p.map(|i| i.map(|p| p.get_bytes().to_vec()))) 72 | } 73 | } 74 | 75 | impl Sink for TunAsyncDevice { 76 | type Error = io::Error; 77 | 78 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 79 | self.dev.poll_ready_unpin(cx) 80 | } 81 | 82 | fn start_send(mut self: Pin<&mut Self>, item: Packet) -> Result<(), Self::Error> { 83 | self.dev.start_send_unpin(TunPacket::new(item)) 84 | } 85 | 86 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 87 | self.dev.poll_flush_unpin(cx) 88 | } 89 | 90 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 91 | self.dev.poll_close_unpin(cx) 92 | } 93 | } 94 | 95 | impl AsyncDevice for TunAsyncDevice { 96 | fn capabilities(&self) -> &DeviceCapabilities { 97 | &self.caps 98 | } 99 | } 100 | 101 | impl TunAsyncDevice { 102 | pub fn name(&self) -> &str { 103 | &self.device_name 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /protocol/raw/src/forward.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::{Ipv4Addr, SocketAddr, SocketAddrV4}, 4 | }; 5 | 6 | use rd_interface::{Arc, Context, IntoAddress, Net, Result, TcpStream}; 7 | use rd_std::{util::forward_udp, ContextExt}; 8 | use tokio::select; 9 | use tokio_smoltcp::{ 10 | smoltcp::wire::{IpCidr, IpProtocol, IpVersion}, 11 | Net as SmoltcpNet, RawSocket, TcpListener, 12 | }; 13 | 14 | use crate::gateway::MapTable; 15 | 16 | mod source; 17 | 18 | struct Forward { 19 | net: Net, 20 | map: MapTable, 21 | ip_cidr: IpCidr, 22 | } 23 | 24 | pub async fn forward_net( 25 | net: Net, 26 | smoltcp_net: Arc, 27 | map: MapTable, 28 | ip_cidr: IpCidr, 29 | ) -> io::Result<()> { 30 | let tcp_listener = smoltcp_net 31 | .tcp_bind(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 1).into()) 32 | .await?; 33 | let raw_socket = smoltcp_net 34 | .raw_socket(IpVersion::Ipv4, IpProtocol::Udp) 35 | .await?; 36 | 37 | let forward = Forward { net, map, ip_cidr }; 38 | 39 | let tcp_task = forward.serve_tcp(tcp_listener); 40 | let udp_task = forward.serve_udp(raw_socket); 41 | 42 | select! { 43 | r = tcp_task => r?, 44 | r = udp_task => r?, 45 | }; 46 | 47 | Ok(()) 48 | } 49 | 50 | impl Forward { 51 | async fn serve_tcp(&self, mut listener: TcpListener) -> Result<()> { 52 | loop { 53 | let (tcp, addr) = listener.accept().await?; 54 | let orig_addr = self.map.get(&match addr { 55 | SocketAddr::V4(v4) => v4, 56 | _ => continue, 57 | }); 58 | if let Some(orig_addr) = orig_addr { 59 | let net = self.net.clone(); 60 | tokio::spawn(async move { 61 | let ctx = &mut Context::from_socketaddr(addr); 62 | let target = net 63 | .tcp_connect(ctx, &SocketAddr::from(orig_addr).into_address()?) 64 | .await?; 65 | ctx.connect_tcp(TcpStream::from(tcp), target).await?; 66 | Ok(()) as Result<()> 67 | }); 68 | } 69 | } 70 | } 71 | async fn serve_udp(&self, raw: RawSocket) -> Result<()> { 72 | let source = source::Source::new(raw, self.ip_cidr); 73 | 74 | forward_udp::forward_udp(source, self.net.clone(), None).await?; 75 | 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /protocol/raw/src/lib.rs: -------------------------------------------------------------------------------- 1 | use net::RawNet; 2 | use rd_interface::{Registry, Result}; 3 | use server::RawServer; 4 | 5 | mod config; 6 | mod device; 7 | mod forward; 8 | mod gateway; 9 | mod net; 10 | mod server; 11 | mod wrap; 12 | 13 | pub fn init(registry: &mut Registry) -> Result<()> { 14 | registry.add_net::(); 15 | registry.add_server::(); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /protocol/raw/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | forward::forward_net, 3 | net::{NetParams, RawNet}, 4 | }; 5 | use rd_interface::{ 6 | async_trait, config::NetRef, prelude::*, rd_config, registry::Builder, Error, IServer, Net, 7 | Result, Server, 8 | }; 9 | 10 | #[rd_config] 11 | pub struct RawServerConfig { 12 | #[serde(default)] 13 | net: NetRef, 14 | /// Must be raw net. 15 | listen: NetRef, 16 | } 17 | 18 | pub struct RawServer { 19 | net: Net, 20 | params: NetParams, 21 | } 22 | 23 | #[async_trait] 24 | impl IServer for RawServer { 25 | async fn start(&self) -> rd_interface::Result<()> { 26 | let params = &self.params; 27 | forward_net( 28 | self.net.clone(), 29 | params.smoltcp_net.clone(), 30 | params.map.clone(), 31 | params.ip_cidr, 32 | ) 33 | .await?; 34 | 35 | Ok(()) 36 | } 37 | } 38 | 39 | impl RawServer { 40 | fn new(config: RawServerConfig) -> Result { 41 | let net = config.net.value_cloned(); 42 | let listen = config.listen.value_cloned(); 43 | let raw_net = listen 44 | .get_inner_net_by::() 45 | .ok_or_else(|| Error::other("net must be `raw` type."))?; 46 | let params = raw_net 47 | .get_params() 48 | .ok_or_else(|| Error::other("The `raw` net must has forward enabled."))?; 49 | 50 | Ok(RawServer { net, params }) 51 | } 52 | } 53 | 54 | impl Builder for RawServer { 55 | const NAME: &'static str = "raw"; 56 | 57 | type Config = RawServerConfig; 58 | type Item = RawServer; 59 | 60 | fn build(config: Self::Config) -> Result { 61 | RawServer::new(config) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /protocol/raw/src/wrap.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::SocketAddr, 4 | task::{self, Poll}, 5 | }; 6 | 7 | use futures::ready; 8 | use rd_interface::{ 9 | async_trait, impl_async_read_write, Address, ITcpListener, ITcpStream, IUdpSocket, IntoDyn, 10 | Result, 11 | }; 12 | use tokio::sync::Mutex; 13 | use tokio_smoltcp::{TcpListener, TcpStream, UdpSocket}; 14 | 15 | pub struct TcpStreamWrap(TcpStream); 16 | 17 | impl TcpStreamWrap { 18 | pub(crate) fn new(stream: TcpStream) -> Self { 19 | Self(stream) 20 | } 21 | } 22 | 23 | #[async_trait] 24 | impl ITcpStream for TcpStreamWrap { 25 | async fn peer_addr(&self) -> Result { 26 | Ok(self.0.peer_addr()?) 27 | } 28 | 29 | async fn local_addr(&self) -> Result { 30 | Ok(self.0.local_addr()?) 31 | } 32 | 33 | impl_async_read_write!(0); 34 | } 35 | 36 | pub struct TcpListenerWrap(pub(crate) Mutex, pub(crate) SocketAddr); 37 | 38 | #[async_trait] 39 | impl ITcpListener for TcpListenerWrap { 40 | async fn accept(&self) -> Result<(rd_interface::TcpStream, SocketAddr)> { 41 | let (tcp, addr) = self.0.lock().await.accept().await?; 42 | Ok((TcpStreamWrap::new(tcp).into_dyn(), addr)) 43 | } 44 | 45 | async fn local_addr(&self) -> Result { 46 | Ok(self.1) 47 | } 48 | } 49 | 50 | pub struct UdpSocketWrap { 51 | inner: UdpSocket, 52 | } 53 | 54 | impl UdpSocketWrap { 55 | pub(crate) fn new(inner: UdpSocket) -> Self { 56 | Self { inner } 57 | } 58 | } 59 | 60 | #[async_trait] 61 | impl IUdpSocket for UdpSocketWrap { 62 | async fn local_addr(&self) -> Result { 63 | Ok(self.inner.local_addr()?) 64 | } 65 | 66 | fn poll_recv_from( 67 | &mut self, 68 | cx: &mut task::Context<'_>, 69 | buf: &mut rd_interface::ReadBuf, 70 | ) -> Poll> { 71 | let UdpSocketWrap { inner, .. } = &mut *self; 72 | let (size, from) = ready!(inner.poll_recv_from(cx, buf.initialize_unfilled()))?; 73 | buf.advance(size); 74 | Poll::Ready(Ok(from)) 75 | } 76 | 77 | fn poll_send_to( 78 | &mut self, 79 | cx: &mut task::Context<'_>, 80 | buf: &[u8], 81 | target: &Address, 82 | ) -> Poll> { 83 | let UdpSocketWrap { inner, .. } = &mut *self; 84 | 85 | // TODO: support domain 86 | let size = ready!(inner.poll_send_to(cx, buf, target.to_socket_addr()?))?; 87 | if size != buf.len() { 88 | return Err(io::Error::new( 89 | io::ErrorKind::Other, 90 | "failed to send all bytes", 91 | )) 92 | .into(); 93 | } 94 | 95 | Poll::Ready(Ok(size)) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /protocol/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rpc" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rd-interface = { path = "../../rd-interface/", version = "0.4" } 9 | rd-std = { path = "../../rd-std/", version = "0.1" } 10 | # rd-interface = "0.3" 11 | serde = { version = "1.0", features = ["derive"] } 12 | tracing = "0.1.26" 13 | tokio = { version = "1.5.0", features = ["rt"] } 14 | uuid = { version = "1.3.0", features = ["v4", "serde"] } 15 | futures = "0.3" 16 | parking_lot = "0.12.0" 17 | serde_json = "1.0.78" 18 | cbor4ii = { version = "0.3.1", features = ["serde1"] } 19 | -------------------------------------------------------------------------------- /protocol/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | use net::RpcNet; 2 | use rd_interface::{ 3 | config::NetRef, prelude::*, rd_config, registry::Builder, Address, Net, Registry, Result, 4 | Server, 5 | }; 6 | use server::RpcServer; 7 | 8 | mod connection; 9 | mod net; 10 | mod server; 11 | mod session; 12 | #[cfg(test)] 13 | mod tests; 14 | mod types; 15 | 16 | #[rd_config] 17 | #[derive(Clone, Copy)] 18 | pub enum Codec { 19 | Json, 20 | Cbor, 21 | } 22 | 23 | impl Default for Codec { 24 | fn default() -> Self { 25 | Codec::Cbor 26 | } 27 | } 28 | 29 | impl From for connection::Codec { 30 | fn from(this: Codec) -> Self { 31 | match this { 32 | Codec::Json => connection::Codec::Json, 33 | Codec::Cbor => connection::Codec::Cbor, 34 | } 35 | } 36 | } 37 | 38 | #[rd_config] 39 | pub struct RpcNetConfig { 40 | #[serde(default)] 41 | net: NetRef, 42 | server: Address, 43 | #[serde(default)] 44 | codec: Codec, 45 | } 46 | 47 | #[rd_config] 48 | pub struct RpcServerConfig { 49 | bind: Address, 50 | 51 | #[serde(default)] 52 | net: NetRef, 53 | #[serde(default)] 54 | listen: NetRef, 55 | #[serde(default)] 56 | codec: Codec, 57 | } 58 | 59 | impl Builder for RpcNet { 60 | const NAME: &'static str = "rpc"; 61 | type Config = RpcNetConfig; 62 | type Item = Self; 63 | 64 | fn build(config: Self::Config) -> Result { 65 | Ok(RpcNet::new( 66 | config.net.value_cloned(), 67 | config.server, 68 | true, 69 | config.codec.into(), 70 | )) 71 | } 72 | } 73 | 74 | impl Builder for RpcServer { 75 | const NAME: &'static str = "rpc"; 76 | type Config = RpcServerConfig; 77 | type Item = Self; 78 | 79 | fn build( 80 | Self::Config { 81 | listen, 82 | net, 83 | bind, 84 | codec, 85 | }: Self::Config, 86 | ) -> Result { 87 | Ok(RpcServer::new( 88 | listen.value_cloned(), 89 | net.value_cloned(), 90 | bind, 91 | codec.into(), 92 | )) 93 | } 94 | } 95 | 96 | pub fn init(registry: &mut Registry) -> Result<()> { 97 | registry.add_net::(); 98 | registry.add_server::(); 99 | 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /protocol/rpc/src/session/state.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::atomic::{AtomicU32, Ordering}, 4 | sync::Arc, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use futures::{ 9 | lock::{Mutex, MutexGuard}, 10 | FutureExt, 11 | }; 12 | use parking_lot::Mutex as SyncMutex; 13 | use rd_interface::Result; 14 | use tokio::sync::oneshot; 15 | use uuid::Uuid; 16 | 17 | use crate::types::{Object, Response}; 18 | 19 | type WaitMap = SyncMutex)>>>; 20 | 21 | pub struct ClientSessionState { 22 | session_id: Uuid, 23 | seq_id: AtomicU32, 24 | wait_map: Arc, 25 | } 26 | 27 | impl ClientSessionState { 28 | pub fn new() -> Self { 29 | Self { 30 | session_id: Uuid::new_v4(), 31 | seq_id: AtomicU32::new(0), 32 | wait_map: Arc::new(SyncMutex::new(HashMap::new())), 33 | } 34 | } 35 | pub fn session_id(&self) -> Uuid { 36 | self.session_id 37 | } 38 | pub fn next_seq_id(&self) -> u32 { 39 | self.seq_id.fetch_add(1, Ordering::Relaxed) 40 | } 41 | pub fn wait_for_response(&self, seq_id: u32) -> oneshot::Receiver<(Response, Vec)> { 42 | let (tx, rx) = oneshot::channel(); 43 | self.wait_map.lock().insert(seq_id, tx); 44 | rx 45 | } 46 | pub fn send_response(&self, resp: Response, data: Vec) { 47 | if let Some(tx) = self.wait_map.lock().remove(&resp.seq_id) { 48 | let _ = tx.send((resp, data)); 49 | } 50 | } 51 | } 52 | 53 | pub struct Shared(Arc>); 54 | 55 | impl Shared { 56 | pub fn new(obj: Obj) -> Self { 57 | Self(Arc::new(Mutex::new(obj))) 58 | } 59 | pub async fn lock(&self) -> MutexGuard<'_, Obj> { 60 | self.0.lock().await 61 | } 62 | pub fn poll_lock(&self, cx: &mut Context<'_>) -> Poll> { 63 | let mut fut = self.0.lock(); 64 | fut.poll_unpin(cx) 65 | } 66 | } 67 | 68 | impl Clone for Shared { 69 | fn clone(&self) -> Self { 70 | Self(self.0.clone()) 71 | } 72 | } 73 | 74 | pub struct ServerSessionState { 75 | objects: SyncMutex>>, 76 | obj_id: SyncMutex, 77 | } 78 | 79 | impl ServerSessionState { 80 | pub fn new() -> Self { 81 | Self { 82 | objects: SyncMutex::new(HashMap::new()), 83 | obj_id: SyncMutex::new(0), 84 | } 85 | } 86 | pub fn insert_object(&self, obj: Obj) -> Object { 87 | let mut obj_id = self.obj_id.lock(); 88 | let id = *obj_id; 89 | *obj_id += 1; 90 | 91 | let key = Object::from_u32(id); 92 | self.objects.lock().insert(key, Shared::new(obj)); 93 | key 94 | } 95 | pub fn remove_object(&self, obj: Object) { 96 | self.objects.lock().remove(&obj); 97 | } 98 | pub fn get_object(&self, obj: Object) -> Result> { 99 | let obj = self 100 | .objects 101 | .lock() 102 | .get(&obj) 103 | .ok_or_else(|| rd_interface::Error::NotFound(format!("Object {:?} not found", obj)))? 104 | .clone(); 105 | Ok(obj) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /protocol/rpc/src/types.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{Address, Value}; 2 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 3 | use uuid::Uuid; 4 | 5 | #[derive(Debug, Clone, Copy, Deserialize, Serialize, Hash, PartialEq, Eq)] 6 | pub struct Object(u32); 7 | 8 | impl Object { 9 | pub fn from_u32(id: u32) -> Self { 10 | Self(id) 11 | } 12 | } 13 | 14 | #[derive(Debug, Deserialize, Serialize)] 15 | pub enum RpcValue { 16 | Null, 17 | Value(Value), 18 | ObjectValue(Object, Value), 19 | } 20 | 21 | #[derive(Debug, Deserialize, Serialize)] 22 | pub enum Error { 23 | ObjectNotFound, 24 | } 25 | 26 | #[derive(Debug, Deserialize, Serialize)] 27 | pub enum Command { 28 | // Get into the session. 29 | Handshake(Uuid), 30 | TcpConnect(Value, Address), 31 | TcpBind(Value, Address), 32 | UdpBind(Value, Address), 33 | LookupHost(Address), 34 | Accept(Object), 35 | Read(Object, u32), 36 | Write(Object), 37 | Flush(Object), 38 | Shutdown(Object), 39 | RecvFrom(Object), 40 | SendTo(Object, Address), 41 | LocalAddr(Object), 42 | PeerAddr(Object), 43 | Close(Object), 44 | } 45 | 46 | #[derive(Debug, Deserialize, Serialize)] 47 | pub struct Request { 48 | pub cmd: Command, 49 | pub seq_id: u32, 50 | } 51 | 52 | #[derive(Debug, Deserialize, Serialize)] 53 | pub struct Response { 54 | pub seq_id: u32, 55 | pub result: Result, 56 | } 57 | 58 | impl Response { 59 | pub fn into_null(self) -> rd_interface::Result<()> { 60 | let val = self.result.map_err(rd_interface::Error::other)?; 61 | 62 | match val { 63 | RpcValue::Null => Ok(()), 64 | _ => Err(rd_interface::Error::other("not null")), 65 | } 66 | } 67 | pub fn into_value(self) -> rd_interface::Result 68 | where 69 | T: DeserializeOwned, 70 | { 71 | let val = self.result.map_err(rd_interface::Error::other)?; 72 | 73 | match val { 74 | RpcValue::Value(v) => Ok(serde_json::from_value(v)?), 75 | _ => Err(rd_interface::Error::other("invalid response")), 76 | } 77 | } 78 | 79 | pub fn into_object_value(self) -> rd_interface::Result<(Object, T)> 80 | where 81 | T: DeserializeOwned, 82 | { 83 | let val = self.result.map_err(rd_interface::Error::other)?; 84 | 85 | match val { 86 | RpcValue::ObjectValue(o, v) => Ok((o, serde_json::from_value(v)?)), 87 | _ => Err(rd_interface::Error::other("invalid response")), 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /protocol/ss/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ss" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rd-interface = { path = "../../rd-interface/", version = "0.4" } 9 | rd-std = { path = "../../rd-std/", version = "0.1" } 10 | # rd-interface = "0.3" 11 | shadowsocks = { version = "=1.13.2", default-features = false, features = [ 12 | "stream-cipher", 13 | "aead-cipher-extra", 14 | ] } 15 | serde = "1.0" 16 | bytes = "1.0" 17 | tracing = "0.1.26" 18 | serde_json = "1.0" 19 | tokio = { version = "1.5.0", features = ["rt"] } 20 | socks5-protocol = "0.3.5" 21 | futures = "0.3" 22 | -------------------------------------------------------------------------------- /protocol/ss/src/lib.rs: -------------------------------------------------------------------------------- 1 | use client::{SSNet, SSNetConfig}; 2 | use rd_interface::{registry::Builder, Net, Registry, Result, Server}; 3 | use server::{SSServer, SSServerConfig}; 4 | 5 | mod client; 6 | mod server; 7 | #[cfg(test)] 8 | mod tests; 9 | mod udp; 10 | mod wrapper; 11 | 12 | impl Builder for SSNet { 13 | const NAME: &'static str = "shadowsocks"; 14 | type Config = SSNetConfig; 15 | type Item = Self; 16 | 17 | fn build(config: Self::Config) -> Result { 18 | Ok(SSNet::new(config)) 19 | } 20 | } 21 | 22 | impl Builder for SSServer { 23 | const NAME: &'static str = "shadowsocks"; 24 | type Config = SSServerConfig; 25 | type Item = Self; 26 | 27 | fn build(cfg: Self::Config) -> Result { 28 | Ok(SSServer::new(cfg)) 29 | } 30 | } 31 | 32 | pub fn init(registry: &mut Registry) -> Result<()> { 33 | registry.add_net::(); 34 | registry.add_server::(); 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /protocol/ss/src/server/source.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | task::{self, Poll}, 4 | }; 5 | 6 | use bytes::BytesMut; 7 | use futures::ready; 8 | use rd_interface::UdpSocket; 9 | use rd_std::util::forward_udp::{RawUdpSource, UdpEndpoint}; 10 | use shadowsocks::crypto::v1::CipherKind; 11 | use socks5_protocol::Address as S5Addr; 12 | 13 | use crate::udp::{decrypt_payload, encrypt_payload}; 14 | 15 | pub struct UdpSource { 16 | udp: UdpSocket, 17 | 18 | method: CipherKind, 19 | key: Box<[u8]>, 20 | send_buf: BytesMut, 21 | } 22 | 23 | impl UdpSource { 24 | pub fn new(method: CipherKind, key: Box<[u8]>, udp: UdpSocket) -> UdpSource { 25 | UdpSource { 26 | udp, 27 | method, 28 | key, 29 | send_buf: BytesMut::new(), 30 | } 31 | } 32 | } 33 | 34 | impl RawUdpSource for UdpSource { 35 | fn poll_recv( 36 | &mut self, 37 | cx: &mut task::Context<'_>, 38 | buf: &mut rd_interface::ReadBuf, 39 | ) -> Poll> { 40 | let packet = loop { 41 | let from = ready!(self.udp.poll_recv_from(cx, buf))?; 42 | let (n, addr) = match decrypt_payload(self.method, &self.key, buf.filled_mut()) { 43 | Ok(r) => r, 44 | Err(e) => { 45 | tracing::warn!("Failed to decode udp packet: {:?}", e); 46 | continue; 47 | } 48 | }; 49 | buf.set_filled(n); 50 | // drop the packet if it's sent to domain silently 51 | let to = match addr { 52 | S5Addr::Domain(_, _) => continue, 53 | S5Addr::SocketAddr(s) => s, 54 | }; 55 | 56 | break UdpEndpoint { from, to }; 57 | }; 58 | 59 | Poll::Ready(Ok(packet)) 60 | } 61 | 62 | fn poll_send( 63 | &mut self, 64 | cx: &mut task::Context<'_>, 65 | buf: &[u8], 66 | endpoint: &UdpEndpoint, 67 | ) -> Poll> { 68 | if self.send_buf.is_empty() { 69 | encrypt_payload( 70 | self.method, 71 | &self.key, 72 | &endpoint.from.into(), 73 | buf, 74 | &mut self.send_buf, 75 | )?; 76 | } 77 | 78 | ready!(self 79 | .udp 80 | .poll_send_to(cx, &self.send_buf, &endpoint.to.into()))?; 81 | self.send_buf.clear(); 82 | 83 | Poll::Ready(Ok(())) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /protocol/ss/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::wrapper::Cipher; 2 | 3 | use super::*; 4 | use rd_interface::{config::NetRef, IServer, IntoAddress, IntoDyn, Value}; 5 | use rd_std::tests::{ 6 | assert_echo, assert_echo_udp, get_registry, spawn_echo_server, spawn_echo_server_udp, TestNet, 7 | }; 8 | use std::time::Duration; 9 | use tokio::time::sleep; 10 | 11 | #[test] 12 | fn test_ss_smoke() { 13 | let mut registry = get_registry(); 14 | super::init(&mut registry).unwrap(); 15 | } 16 | 17 | #[tokio::test] 18 | async fn test_ss_server_client() { 19 | let local = TestNet::new().into_dyn(); 20 | spawn_echo_server(&local, "127.0.0.1:26666").await; 21 | spawn_echo_server_udp(&local, "127.0.0.1:26666").await; 22 | 23 | let server_addr = "127.0.0.1:16666".into_address().unwrap(); 24 | let server_cfg = server::SSServerConfig { 25 | listen: NetRef::new_with_value("local".to_string().into(), local.clone()), 26 | net: NetRef::new_with_value("local".to_string().into(), local.clone()), 27 | bind: server_addr.clone(), 28 | password: "password".into(), 29 | udp: true, 30 | cipher: Cipher::AES_128_GCM, 31 | }; 32 | let server = server::SSServer::new(server_cfg); 33 | tokio::spawn(async move { server.start().await }); 34 | 35 | sleep(Duration::from_secs(1)).await; 36 | 37 | let client_cfg = client::SSNetConfig { 38 | server: "localhost:16666".into_address().unwrap(), 39 | password: "password".into(), 40 | udp: true, 41 | cipher: Cipher::AES_128_GCM, 42 | net: NetRef::new_with_value(Value::String("local".to_string()), local.clone()), 43 | }; 44 | let client = client::SSNet::new(client_cfg).into_dyn(); 45 | 46 | assert_echo(&client, "127.0.0.1:26666").await; 47 | assert_echo_udp(&client, "127.0.0.1:26666").await; 48 | } 49 | -------------------------------------------------------------------------------- /protocol/trojan/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trojan" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rd-interface = { path = "../../rd-interface/", version = "0.4" } 9 | rd-std = { path = "../../rd-std/", version = "0.1" } 10 | serde = "1.0" 11 | sha2 = "0.10.1" 12 | hex = "0.4.3" 13 | socks5-protocol = "0.3.4" 14 | futures = "0.3" 15 | tokio = "1.0" 16 | tokio-tungstenite = "0.20.0" 17 | tokio-util = { version = "0.7.1", features = ["codec", "net"] } 18 | bytes = "1.1.0" 19 | -------------------------------------------------------------------------------- /protocol/trojan/src/client/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::{io, net::SocketAddr, pin::Pin, task, time::Duration}; 2 | 3 | use crate::stream::IOStream; 4 | use futures::{ready, FutureExt}; 5 | use rd_interface::{async_trait, AsyncRead, AsyncWrite, ITcpStream, ReadBuf, NOT_IMPLEMENTED}; 6 | use tokio::time::{sleep, Sleep}; 7 | 8 | pub(super) struct TrojanTcp { 9 | stream: Box, 10 | head: Option>, 11 | is_first: bool, 12 | sleep: Pin>, 13 | } 14 | 15 | impl TrojanTcp { 16 | pub fn new(stream: Box, head: Vec) -> Self { 17 | Self { 18 | stream, 19 | head: Some(head), 20 | is_first: true, 21 | sleep: Box::pin(sleep(Duration::from_millis(100))), 22 | } 23 | } 24 | fn poll_send_head( 25 | &mut self, 26 | cx: &mut task::Context<'_>, 27 | buf: &[u8], 28 | ) -> task::Poll> { 29 | loop { 30 | let Self { 31 | stream, 32 | head, 33 | is_first, 34 | .. 35 | } = &mut *self; 36 | let stream = Pin::new(stream); 37 | 38 | let len = match head { 39 | Some(head) => { 40 | if *is_first { 41 | head.extend(buf); 42 | *is_first = false; 43 | } 44 | 45 | let sent = ready!(stream.poll_write(cx, head))?; 46 | head.drain(..sent); 47 | head.len() 48 | } 49 | None => break, 50 | }; 51 | if len == 0 { 52 | *head = None; 53 | return task::Poll::Ready(Ok(buf.len())); 54 | } 55 | } 56 | 57 | task::Poll::Ready(Ok(0)) 58 | } 59 | } 60 | 61 | #[async_trait] 62 | impl ITcpStream for TrojanTcp { 63 | async fn peer_addr(&self) -> rd_interface::Result { 64 | Err(NOT_IMPLEMENTED) 65 | } 66 | 67 | async fn local_addr(&self) -> rd_interface::Result { 68 | Err(NOT_IMPLEMENTED) 69 | } 70 | 71 | fn poll_read( 72 | &mut self, 73 | cx: &mut task::Context<'_>, 74 | buf: &mut ReadBuf<'_>, 75 | ) -> task::Poll> { 76 | if self.sleep.is_elapsed() { 77 | ready!(self.poll_send_head(cx, &[]))?; 78 | } else { 79 | let _ = self.sleep.poll_unpin(cx); 80 | } 81 | 82 | Pin::new(&mut self.stream).poll_read(cx, buf) 83 | } 84 | 85 | fn poll_write( 86 | &mut self, 87 | cx: &mut task::Context<'_>, 88 | buf: &[u8], 89 | ) -> task::Poll> { 90 | let len = ready!(self.poll_send_head(cx, &buf))?; 91 | if len > 0 { 92 | return task::Poll::Ready(Ok(len)); 93 | } 94 | 95 | Pin::new(&mut self.stream).poll_write(cx, buf) 96 | } 97 | 98 | fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { 99 | Pin::new(&mut self.stream).poll_flush(cx) 100 | } 101 | 102 | fn poll_shutdown(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { 103 | Pin::new(&mut self.stream).poll_shutdown(cx) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /protocol/trojan/src/lib.rs: -------------------------------------------------------------------------------- 1 | use client::{TrojanNet, TrojanNetConfig, TrojancNetConfig}; 2 | use rd_interface::{registry::Builder, Net, Registry, Result}; 3 | 4 | mod client; 5 | mod stream; 6 | mod websocket; 7 | 8 | impl Builder for TrojanNet { 9 | const NAME: &'static str = "trojan"; 10 | type Config = TrojanNetConfig; 11 | type Item = Self; 12 | 13 | fn build(config: Self::Config) -> Result { 14 | TrojanNet::new_trojan(config) 15 | } 16 | } 17 | 18 | pub struct TrojancNet; 19 | impl Builder for TrojancNet { 20 | const NAME: &'static str = "trojanc"; 21 | type Config = TrojancNetConfig; 22 | type Item = TrojanNet; 23 | 24 | fn build(config: Self::Config) -> Result { 25 | TrojanNet::new_trojanc(config) 26 | } 27 | } 28 | 29 | pub fn init(registry: &mut Registry) -> Result<()> { 30 | registry.add_net::(); 31 | registry.add_net::(); 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /protocol/trojan/src/stream.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{AsyncRead, AsyncWrite}; 2 | 3 | pub trait IOStream: AsyncRead + AsyncWrite + Unpin + Send + Sync {} 4 | 5 | impl IOStream for T where T: AsyncRead + AsyncWrite + Unpin + Send + Sync {} 6 | -------------------------------------------------------------------------------- /protocol/trojan/src/websocket.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | io, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use crate::stream::IOStream; 9 | use futures::{ready, SinkExt, StreamExt}; 10 | use rd_interface::{error::map_other, AsyncRead, AsyncWrite, ReadBuf, Result}; 11 | use tokio_tungstenite::{client_async, tungstenite::Message}; 12 | 13 | pub struct WebSocketStream { 14 | client: tokio_tungstenite::WebSocketStream, 15 | read: VecDeque, 16 | wrote: usize, 17 | } 18 | 19 | impl WebSocketStream { 20 | pub async fn connect(stream: S, host: &str, path: &str) -> Result { 21 | let url = format!("wss://{}/{}", host, path.trim_start_matches('/')); 22 | let (client, _resp) = client_async(url, stream).await.map_err(map_other)?; 23 | 24 | Ok(WebSocketStream { 25 | client, 26 | read: VecDeque::new(), 27 | wrote: 0, 28 | }) 29 | } 30 | } 31 | 32 | impl AsyncRead for WebSocketStream { 33 | fn poll_read( 34 | mut self: Pin<&mut Self>, 35 | cx: &mut Context<'_>, 36 | buf: &mut ReadBuf<'_>, 37 | ) -> Poll> { 38 | if self.read.is_empty() { 39 | let msg = loop { 40 | let msg = match ready!(self.client.poll_next_unpin(cx)) { 41 | Some(msg) => msg.map_err(map_other)?, 42 | None => return Poll::Ready(Ok(())), 43 | }; 44 | 45 | match msg { 46 | Message::Binary(b) => break b, 47 | Message::Close(_) => return Poll::Ready(Ok(())), 48 | _ => {} 49 | } 50 | }; 51 | self.read.extend(msg); 52 | } 53 | 54 | let (first, _) = self.read.as_slices(); 55 | 56 | let to_read = first.len().min(buf.remaining()); 57 | buf.initialize_unfilled_to(to_read) 58 | .copy_from_slice(&first[..to_read]); 59 | 60 | buf.advance(to_read); 61 | self.read.drain(..to_read); 62 | 63 | Poll::Ready(Ok(())) 64 | } 65 | } 66 | 67 | fn io_err(e: impl std::error::Error + Send + Sync + 'static) -> io::Error { 68 | io::Error::new(io::ErrorKind::Other, e) 69 | } 70 | 71 | impl AsyncWrite for WebSocketStream { 72 | fn poll_write( 73 | mut self: Pin<&mut Self>, 74 | cx: &mut Context<'_>, 75 | buf: &[u8], 76 | ) -> Poll> { 77 | if self.wrote == 0 { 78 | ready!(self.client.poll_ready_unpin(cx).map_err(map_other)?); 79 | 80 | self.client 81 | .start_send_unpin(Message::binary(buf)) 82 | .map_err(map_other)?; 83 | self.wrote = buf.len(); 84 | } 85 | 86 | ready!(self.client.poll_flush_unpin(cx).map_err(map_other))?; 87 | let result = Ok(self.wrote); 88 | self.wrote = 0; 89 | 90 | Poll::Ready(result) 91 | } 92 | 93 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 94 | self.client.poll_ready_unpin(cx).map_err(io_err) 95 | } 96 | 97 | fn poll_shutdown( 98 | mut self: Pin<&mut Self>, 99 | cx: &mut Context<'_>, 100 | ) -> Poll> { 101 | self.client.poll_close_unpin(cx).map_err(io_err) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /rabbit-digger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rabbit-digger" 3 | version = "0.1.1" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | description = "Just a stub" 7 | license = "MIT OR Apache-2.0" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = "1.0.38" 13 | rd-interface = { path = "../rd-interface", version = "0.4" } 14 | rd-std = { path = "../rd-std", version = "0.1", optional = true } 15 | futures = "0.3.5" 16 | serde = { version = "1.0.119", features = ["rc"] } 17 | tokio = { version = "1.12.0", features = ["full"] } 18 | tracing = "0.1.26" 19 | serde_json = "1.0" 20 | uuid = { version = "1.3.0", features = ["v4", "serde"] } 21 | topological-sort = "0.1" 22 | parking_lot = "0.12.0" 23 | atomic-shim = "0.2.0" 24 | dashmap = { version = "5.2.0", features = ["serde"] } 25 | indexmap = { version = "2.0.0", features = ["serde"] } 26 | tokio-stream = { version = "0.1.6", features = ["net", "sync", "time"] } 27 | 28 | [dev-dependencies] 29 | rusty-hook = "0.11.0" 30 | tokio = { version = "1.5.0", features = ["macros"] } 31 | 32 | [features] 33 | default = ["rd-std"] 34 | -------------------------------------------------------------------------------- /rabbit-digger/README.md: -------------------------------------------------------------------------------- 1 | # rabbit-digger 2 | 3 | Rule based proxy written in Rust. 4 | -------------------------------------------------------------------------------- /rabbit-digger/src/builtin.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::Result; 2 | 3 | use crate::registry::Registry; 4 | 5 | pub fn load_builtin(registry: &mut Registry) -> Result<()> { 6 | #[cfg(feature = "rd-std")] 7 | registry.init_with_registry("std", rd_std::init)?; 8 | 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /rabbit-digger/src/config/default.rs: -------------------------------------------------------------------------------- 1 | pub fn local_string() -> String { 2 | "local".to_string() 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn test_local_string() { 11 | assert_eq!(local_string(), "local"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /rabbit-digger/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod builtin; 2 | pub mod config; 3 | 4 | mod rabbit_digger; 5 | pub mod registry; 6 | pub mod util; 7 | 8 | pub use config::Config; 9 | pub use registry::Registry; 10 | 11 | pub use rd_interface; 12 | #[cfg(feature = "rd-std")] 13 | pub use rd_std; 14 | 15 | pub use self::rabbit_digger::RabbitDigger; 16 | pub use uuid::Uuid; 17 | -------------------------------------------------------------------------------- /rabbit-digger/src/rabbit_digger/event.rs: -------------------------------------------------------------------------------- 1 | use core::mem::discriminant; 2 | use std::time::SystemTime; 3 | 4 | use rd_interface::{Address, Value}; 5 | use tokio::sync::oneshot; 6 | use uuid::Uuid; 7 | 8 | #[derive(Debug)] 9 | pub enum EventType { 10 | NewTcp(Address, Value), 11 | NewUdp(Address, Value), 12 | SetStopper(oneshot::Sender<()>), 13 | CloseConnection, 14 | Write(u64), 15 | Read(u64), 16 | #[allow(dead_code)] 17 | SendTo(Address, u64), 18 | #[allow(dead_code)] 19 | RecvFrom(Address, u64), 20 | } 21 | 22 | impl PartialEq for EventType { 23 | fn eq(&self, other: &Self) -> bool { 24 | match (self, other) { 25 | (Self::SetStopper(_), Self::SetStopper(_)) => true, 26 | _ => discriminant(self) == discriminant(other), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug)] 32 | pub struct Event { 33 | pub uuid: Uuid, 34 | pub events: Vec, 35 | pub time: SystemTime, 36 | } 37 | 38 | impl Event { 39 | pub fn new(uuid: Uuid, events: Vec) -> Event { 40 | Event { 41 | uuid, 42 | events, 43 | time: SystemTime::now(), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rd-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rd-derive" 3 | version = "0.1.1" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | description = "Derive for easier create rabbit-digger config." 7 | license = "MIT OR Apache-2.0" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | syn = "2.0.27" 13 | quote = "1.0.32" 14 | proc-macro2 = "1.0" 15 | darling = "0.20.3" 16 | 17 | [lib] 18 | proc-macro = true 19 | -------------------------------------------------------------------------------- /rd-interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rd-interface" 3 | version = "0.4.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | description = "Just a stub" 7 | license = "MIT OR Apache-2.0" 8 | 9 | [dependencies] 10 | futures-util = { version = "0.3.12", default-features = false } 11 | async-trait = "0.1.42" 12 | thiserror = "1.0" 13 | serde_json = { version = "1.0", features = ["std", "preserve_order"] } 14 | serde = { version = "1.0.119", features = ["rc", "derive"] } 15 | tokio = { version = "1.5", features = ["io-util", "time"] } 16 | rd-derive = { version = "0.1", path = "../rd-derive" } 17 | schemars = "0.8.3" 18 | 19 | [dev-dependencies] 20 | tokio = { version = "1.17.0", features = ["full"] } 21 | -------------------------------------------------------------------------------- /rd-interface/README.md: -------------------------------------------------------------------------------- 1 | # rd-interface 2 | 3 | Interface defines of rabbit-digger's plugin. 4 | -------------------------------------------------------------------------------- /rd-interface/src/constant.rs: -------------------------------------------------------------------------------- 1 | pub const UDP_BUFFER_SIZE: usize = 8 * 1024; 2 | -------------------------------------------------------------------------------- /rd-interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use address::{Address, AddressDomain, IntoAddress}; 2 | pub use context::Context; 3 | pub use error::{Error, ErrorContext, Result, NOT_IMPLEMENTED}; 4 | pub use interface::*; 5 | pub use rd_derive::{rd_config, Config}; 6 | pub use registry::Registry; 7 | pub use schemars; 8 | pub use serde_json::Value; 9 | 10 | mod address; 11 | pub mod config; 12 | pub mod constant; 13 | pub mod context; 14 | pub mod error; 15 | mod interface; 16 | mod macros; 17 | pub mod registry; 18 | 19 | /// Prelude for easy defining `Config` struct. 20 | pub mod prelude { 21 | pub use rd_derive::rd_config; 22 | pub use schemars; 23 | } 24 | -------------------------------------------------------------------------------- /rd-interface/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_async_read_write { 3 | ($f:tt) => { 4 | rd_interface::impl_async_read!($f); 5 | rd_interface::impl_async_write!($f); 6 | }; 7 | } 8 | 9 | #[macro_export] 10 | macro_rules! impl_async_read { 11 | ($f:tt) => { 12 | fn poll_read( 13 | &mut self, 14 | cx: &mut ::std::task::Context<'_>, 15 | buf: &mut ::rd_interface::ReadBuf, 16 | ) -> ::std::task::Poll<::std::io::Result<()>> { 17 | ::rd_interface::AsyncRead::poll_read(::std::pin::Pin::new(&mut self.$f), cx, buf) 18 | } 19 | }; 20 | } 21 | 22 | #[macro_export] 23 | macro_rules! impl_async_write { 24 | ($f:tt) => { 25 | fn poll_write( 26 | &mut self, 27 | cx: &mut ::std::task::Context<'_>, 28 | buf: &[u8], 29 | ) -> ::std::task::Poll<::std::io::Result> { 30 | ::rd_interface::AsyncWrite::poll_write(::std::pin::Pin::new(&mut self.$f), cx, buf) 31 | } 32 | 33 | fn poll_flush( 34 | &mut self, 35 | cx: &mut ::std::task::Context<'_>, 36 | ) -> ::std::task::Poll<::std::io::Result<()>> { 37 | ::rd_interface::AsyncWrite::poll_flush(::std::pin::Pin::new(&mut self.$f), cx) 38 | } 39 | 40 | fn poll_shutdown( 41 | &mut self, 42 | cx: &mut ::std::task::Context<'_>, 43 | ) -> ::std::task::Poll<::std::io::Result<()>> { 44 | ::rd_interface::AsyncWrite::poll_shutdown(::std::pin::Pin::new(&mut self.$f), cx) 45 | } 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /rd-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rd-std" 3 | version = "0.1.0" 4 | authors = ["spacemeowx2 "] 5 | edition = "2021" 6 | description = "std for rabbit-digger" 7 | license = "MIT OR Apache-2.0" 8 | 9 | [dependencies] 10 | # common 11 | rd-interface = { version = "0.4", path = "../rd-interface" } 12 | rd-derive = { version = "0.1", path = "../rd-derive" } 13 | futures = "0.3" 14 | serde = "1.0" 15 | tracing = "0.1.26" 16 | anyhow = "1.0" 17 | tokio = { version = "1.29.1", features = ["net", "rt", "macros"] } 18 | parking_lot = "0.12.0" 19 | tokio-util = { version = "0.7.1", features = ["codec", "net"] } 20 | pin-project-lite = "0.2.8" 21 | itertools = "0.11.0" 22 | 23 | # socks5 24 | socks5-protocol = "0.3.2" 25 | 26 | # http 27 | http = { version = "0.2.4", optional = true } 28 | hyper = { version = "0.14.12", features = ["http1", "client", "server"] } 29 | base64 = "0.21.5" 30 | 31 | # transparent 32 | libc = "0.2.91" 33 | socket2 = { version = "0.5.3", features = ["all"] } 34 | cfg-if = "1.0" 35 | 36 | # rule 37 | smoltcp = { version = "0.8.0", default-features = false, features = [ 38 | "std", 39 | "proto-ipv4", 40 | "proto-ipv6", 41 | ] } 42 | lru_time_cache = "0.11" 43 | serde_with = "3.1.0" 44 | maxminddb = "0.23.0" 45 | flate2 = "1.0.20" 46 | tar = "0.4.35" 47 | once_cell = "1.7.2" 48 | 49 | # dns 50 | trust-dns-proto = "0.21.1" 51 | trust-dns-resolver = { version = "0.21.1", optional = true } 52 | 53 | # tls 54 | tokio-rustls = { version = "0.24.1", features = [ 55 | "dangerous_configuration", 56 | ], optional = true } 57 | webpki-roots = { version = "0.25.1", optional = true } 58 | 59 | openssl-crate = { package = "openssl", version = "0.10", features = [ 60 | "vendored", 61 | ], optional = true } 62 | tokio-openssl = { version = "0.6.1", optional = true } 63 | 64 | tokio-native-tls = { version = "0.3.0", optional = true } 65 | native-tls-crate = { package = "native-tls", version = "0.2", features = [ 66 | "vendored", 67 | ], optional = true } 68 | 69 | # sni-sniffer 70 | tls-parser = "0.11.0" 71 | 72 | [dev-dependencies] 73 | tokio = { version = "1.29.1", features = ["full"] } 74 | 75 | [features] 76 | default = ["trust-dns-resolver", "native-tls"] 77 | plugin = [] 78 | 79 | rustls = ["tokio-rustls", "webpki-roots"] 80 | openssl = ["openssl-crate", "tokio-openssl"] 81 | native-tls = ["tokio-native-tls", "native-tls-crate"] 82 | -------------------------------------------------------------------------------- /rd-std/src/builtin.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{Registry, Result}; 2 | 3 | pub mod alias; 4 | pub mod blackhole; 5 | pub mod combine; 6 | pub mod dns; 7 | pub mod echo; 8 | pub mod forward; 9 | pub mod local; 10 | pub mod noop; 11 | pub mod resolve; 12 | 13 | pub fn init(registry: &mut Registry) -> Result<()> { 14 | registry.add_net::(); 15 | registry.add_net::(); 16 | registry.add_net::(); 17 | registry.add_net::(); 18 | registry.add_net::(); 19 | registry.add_net::(); 20 | registry.add_net::(); 21 | 22 | registry.add_server::(); 23 | registry.add_server::(); 24 | 25 | Ok(()) 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn test_init() { 34 | let mut registry = Registry::new(); 35 | init(&mut registry).unwrap(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rd-std/src/builtin/alias.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{config::NetRef, prelude::*, registry::Builder, INet, Net, Result}; 2 | 3 | pub struct AliasNet(rd_interface::Net); 4 | 5 | impl AliasNet { 6 | fn new(net: rd_interface::Net) -> AliasNet { 7 | AliasNet(net) 8 | } 9 | } 10 | 11 | impl INet for AliasNet { 12 | fn provide_tcp_connect(&self) -> Option<&dyn rd_interface::TcpConnect> { 13 | self.0.provide_tcp_connect() 14 | } 15 | 16 | fn provide_tcp_bind(&self) -> Option<&dyn rd_interface::TcpBind> { 17 | self.0.provide_tcp_bind() 18 | } 19 | 20 | fn provide_udp_bind(&self) -> Option<&dyn rd_interface::UdpBind> { 21 | self.0.provide_udp_bind() 22 | } 23 | 24 | fn provide_lookup_host(&self) -> Option<&dyn rd_interface::LookupHost> { 25 | self.0.provide_lookup_host() 26 | } 27 | } 28 | 29 | /// A net refering to another net. 30 | #[rd_config] 31 | #[derive(Debug)] 32 | pub struct AliasNetConfig { 33 | net: NetRef, 34 | } 35 | 36 | impl Builder for AliasNet { 37 | const NAME: &'static str = "alias"; 38 | type Config = AliasNetConfig; 39 | type Item = Self; 40 | 41 | fn build(config: Self::Config) -> Result { 42 | Ok(AliasNet::new(config.net.value_cloned())) 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use rd_interface::IntoDyn; 49 | 50 | use super::*; 51 | use crate::tests::{ 52 | assert_echo, assert_echo_udp, assert_net_provider, spawn_echo_server, 53 | spawn_echo_server_udp, ProviderCapability, TestNet, 54 | }; 55 | 56 | #[test] 57 | fn test_provider() { 58 | let net = TestNet::new().into_dyn(); 59 | 60 | let alias = AliasNet::new(net).into_dyn(); 61 | 62 | assert_net_provider( 63 | &alias, 64 | ProviderCapability { 65 | tcp_connect: true, 66 | tcp_bind: true, 67 | udp_bind: true, 68 | lookup_host: true, 69 | }, 70 | ); 71 | } 72 | 73 | #[tokio::test] 74 | async fn test_alias_net() { 75 | let parent_net = TestNet::new().into_dyn(); 76 | let net = AliasNet::new(parent_net.clone()).into_dyn(); 77 | 78 | spawn_echo_server(&net, "127.0.0.1:26666").await; 79 | assert_echo(&parent_net, "127.0.0.1:26666").await; 80 | 81 | spawn_echo_server_udp(&parent_net, "127.0.0.1:26666").await; 82 | assert_echo_udp(&net, "127.0.0.1:26666").await; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rd-std/src/builtin/echo.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{ 2 | async_trait, config::NetRef, prelude::*, registry::Builder, Address, Context, IServer, Net, 3 | Result, Server, TcpListener, TcpStream, 4 | }; 5 | use tokio::io; 6 | use tracing::instrument; 7 | 8 | /// A echo server. 9 | #[rd_config] 10 | #[derive(Debug)] 11 | pub struct EchoServerConfig { 12 | bind: Address, 13 | #[serde(default)] 14 | listen: NetRef, 15 | } 16 | 17 | pub struct EchoServer { 18 | listen: Net, 19 | bind: Address, 20 | } 21 | 22 | impl EchoServer { 23 | fn new(EchoServerConfig { bind, listen }: EchoServerConfig) -> EchoServer { 24 | let listen = listen.value_cloned(); 25 | EchoServer { listen, bind } 26 | } 27 | } 28 | #[async_trait] 29 | impl IServer for EchoServer { 30 | async fn start(&self) -> Result<()> { 31 | let listener = self 32 | .listen 33 | .tcp_bind(&mut Context::new(), &self.bind) 34 | .await?; 35 | self.serve_listener(listener).await 36 | } 37 | } 38 | 39 | impl EchoServer { 40 | #[instrument(err, skip(socket))] 41 | async fn serve_connection(socket: TcpStream) -> Result<()> { 42 | let (mut rx, mut tx) = io::split(socket); 43 | io::copy(&mut rx, &mut tx).await?; 44 | Ok(()) 45 | } 46 | pub async fn serve_listener(&self, listener: TcpListener) -> Result<()> { 47 | loop { 48 | let (socket, _) = listener.accept().await?; 49 | let _ = tokio::spawn(async move { 50 | if let Err(e) = Self::serve_connection(socket).await { 51 | tracing::error!("Error when serve_connection: {:?}", e); 52 | } 53 | }); 54 | } 55 | } 56 | } 57 | 58 | impl Builder for EchoServer { 59 | const NAME: &'static str = "echo"; 60 | type Config = EchoServerConfig; 61 | type Item = Self; 62 | 63 | fn build(cfg: Self::Config) -> Result { 64 | Ok(EchoServer::new(cfg)) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use std::time::Duration; 71 | 72 | use rd_interface::{IntoAddress, IntoDyn}; 73 | use tokio::time::sleep; 74 | 75 | use super::*; 76 | use crate::tests::{assert_echo, TestNet}; 77 | 78 | #[tokio::test] 79 | async fn test_echo_server() { 80 | let net = TestNet::new().into_dyn(); 81 | 82 | let server = EchoServer { 83 | listen: net.clone(), 84 | bind: "127.0.0.1:1234".into_address().unwrap(), 85 | }; 86 | tokio::spawn(async move { server.start().await.unwrap() }); 87 | 88 | sleep(Duration::from_millis(1)).await; 89 | 90 | assert_echo(&net, "127.0.0.1:1234").await; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /rd-std/src/builtin/noop.rs: -------------------------------------------------------------------------------- 1 | use crate::util::NotImplementedNet; 2 | use rd_interface::{config::EmptyConfig, registry::Builder, Net, Result}; 3 | 4 | pub struct NoopNet; 5 | 6 | impl Builder for NoopNet { 7 | const NAME: &'static str = "noop"; 8 | type Config = EmptyConfig; 9 | type Item = NotImplementedNet; 10 | 11 | fn build(_config: Self::Config) -> Result { 12 | Ok(NotImplementedNet) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rd-std/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use connect_tcp::connect_tcp; 4 | use connect_udp::connect_udp; 5 | use rd_interface::{async_trait, AsyncRead, AsyncWrite, Context, UdpChannel, UdpSocket}; 6 | 7 | mod connect_tcp; 8 | mod connect_udp; 9 | 10 | #[async_trait] 11 | pub trait ContextExt { 12 | async fn connect_udp(&mut self, a: UdpChannel, b: UdpSocket) -> io::Result<()>; 13 | async fn connect_tcp(&mut self, a: A, b: B) -> io::Result<()> 14 | where 15 | A: AsyncRead + AsyncWrite + Unpin + Send + 'static, 16 | B: AsyncRead + AsyncWrite + Unpin + Send + 'static; 17 | } 18 | 19 | #[async_trait] 20 | impl ContextExt for Context { 21 | async fn connect_udp(&mut self, a: UdpChannel, b: UdpSocket) -> io::Result<()> { 22 | connect_udp(self, a, b).await 23 | } 24 | 25 | async fn connect_tcp(&mut self, a: A, b: B) -> io::Result<()> 26 | where 27 | A: AsyncRead + AsyncWrite + Unpin + Send + 'static, 28 | B: AsyncRead + AsyncWrite + Unpin + Send + 'static, 29 | { 30 | connect_tcp(self, a, b).await 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rd-std/src/http.rs: -------------------------------------------------------------------------------- 1 | pub use self::{client::HttpClient, server::HttpServer}; 2 | 3 | use rd_interface::{ 4 | prelude::*, 5 | registry::{Builder, NetRef}, 6 | Address, Net, Registry, Result, Server, 7 | }; 8 | 9 | mod client; 10 | mod server; 11 | #[cfg(test)] 12 | mod tests; 13 | 14 | #[rd_config] 15 | #[derive(Debug)] 16 | pub struct HttpNetConfig { 17 | server: Address, 18 | 19 | #[serde(default)] 20 | net: NetRef, 21 | } 22 | 23 | #[rd_config] 24 | #[derive(Debug)] 25 | pub struct AuthConfig { 26 | username: String, 27 | password: String, 28 | } 29 | 30 | #[rd_config] 31 | #[derive(Debug)] 32 | pub struct HttpServerConfig { 33 | bind: Address, 34 | #[serde(default)] 35 | net: NetRef, 36 | #[serde(default)] 37 | listen: NetRef, 38 | #[serde(default)] 39 | auth: Option, 40 | } 41 | 42 | impl Default for HttpServerConfig { 43 | fn default() -> Self { 44 | Self { 45 | bind: Address::SocketAddr("127.0.0.1:0".parse().unwrap()), 46 | net: Default::default(), 47 | listen: Default::default(), 48 | auth: None, 49 | } 50 | } 51 | } 52 | 53 | impl Builder for HttpClient { 54 | const NAME: &'static str = "http"; 55 | type Config = HttpNetConfig; 56 | type Item = Self; 57 | 58 | fn build(config: Self::Config) -> Result { 59 | Ok(HttpClient::new(config.net.value_cloned(), config.server)) 60 | } 61 | } 62 | 63 | impl Builder for server::Http { 64 | const NAME: &'static str = "http"; 65 | type Config = HttpServerConfig; 66 | type Item = Self; 67 | 68 | fn build( 69 | Self::Config { 70 | listen, 71 | net, 72 | bind, 73 | auth, 74 | }: Self::Config, 75 | ) -> Result { 76 | if let Some(auth) = auth { 77 | Ok(server::Http::with_auth( 78 | listen.value_cloned(), 79 | net.value_cloned(), 80 | bind, 81 | auth.username, 82 | auth.password, 83 | )) 84 | } else { 85 | Ok(server::Http::new( 86 | listen.value_cloned(), 87 | net.value_cloned(), 88 | bind, 89 | )) 90 | } 91 | } 92 | } 93 | 94 | pub fn init(registry: &mut Registry) -> Result<()> { 95 | registry.add_net::(); 96 | registry.add_server::(); 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /rd-std/src/http/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::tests::{assert_echo, get_registry, spawn_echo_server, TestNet}; 3 | use rd_interface::IntoAddress; 4 | use rd_interface::{IServer, IntoDyn}; 5 | use std::time::Duration; 6 | use tokio::time::sleep; 7 | 8 | #[test] 9 | fn test_http_smoke() { 10 | let mut registry = get_registry(); 11 | super::init(&mut registry).unwrap(); 12 | } 13 | 14 | #[tokio::test] 15 | async fn test_http_server_client() { 16 | let local = TestNet::new().into_dyn(); 17 | spawn_echo_server(&local, "127.0.0.1:26667").await; 18 | 19 | let server = server::Http::new( 20 | local.clone(), 21 | local.clone(), 22 | "127.0.0.1:16667".into_address().unwrap(), 23 | ); 24 | tokio::spawn(async move { server.start().await }); 25 | 26 | sleep(Duration::from_secs(1)).await; 27 | 28 | let client = 29 | client::HttpClient::new(local, "127.0.0.1:16667".into_address().unwrap()).into_dyn(); 30 | 31 | assert_echo(&client, "127.0.0.1:26667").await; 32 | } 33 | 34 | #[tokio::test] 35 | async fn test_http_server_auth() { 36 | let local = TestNet::new().into_dyn(); 37 | spawn_echo_server(&local, "127.0.0.1:26668").await; 38 | 39 | // 创建带认证的服务器 40 | let server = server::Http::with_auth( 41 | local.clone(), 42 | local.clone(), 43 | "127.0.0.1:16668".into_address().unwrap(), 44 | "testuser".to_string(), 45 | "testpass".to_string(), 46 | ); 47 | tokio::spawn(async move { server.start().await }); 48 | 49 | sleep(Duration::from_secs(1)).await; 50 | 51 | // 使用带认证的客户端 52 | let client = client::HttpClient::with_auth( 53 | local.clone(), 54 | "127.0.0.1:16668".into_address().unwrap(), 55 | "testuser".to_string(), 56 | "testpass".to_string(), 57 | ) 58 | .into_dyn(); 59 | 60 | assert_echo(&client, "127.0.0.1:26668").await; 61 | } 62 | -------------------------------------------------------------------------------- /rd-std/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use context::ContextExt; 2 | use rd_interface::{Registry, Result}; 3 | 4 | pub mod builtin; 5 | mod context; 6 | pub mod http; 7 | pub mod mixed; 8 | pub mod rule; 9 | pub mod sniffer; 10 | pub mod socks5; 11 | pub mod tests; 12 | pub mod tls; 13 | pub mod transparent; 14 | pub mod util; 15 | 16 | pub fn init(registry: &mut Registry) -> Result<()> { 17 | builtin::init(registry)?; 18 | sniffer::init(registry)?; 19 | http::init(registry)?; 20 | mixed::init(registry)?; 21 | tls::init(registry)?; 22 | transparent::init(registry)?; 23 | rule::init(registry)?; 24 | socks5::init(registry)?; 25 | Ok(()) 26 | } 27 | 28 | #[cfg(test)] 29 | mod init_tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn test_std_init() { 34 | let mut registry = Registry::new(); 35 | init(&mut registry).unwrap(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rd-std/src/rule.rs: -------------------------------------------------------------------------------- 1 | mod any; 2 | pub mod config; 3 | mod domain; 4 | mod geoip; 5 | mod ipcidr; 6 | mod matcher; 7 | mod rule_net; 8 | 9 | use rd_interface::{registry::Builder, Net, Registry, Result}; 10 | 11 | impl Builder for rule_net::RuleNet { 12 | const NAME: &'static str = "rule"; 13 | type Config = config::RuleNetConfig; 14 | type Item = Self; 15 | 16 | fn build(config: Self::Config) -> Result { 17 | rule_net::RuleNet::new(config) 18 | } 19 | } 20 | 21 | pub fn init(registry: &mut Registry) -> Result<()> { 22 | registry.add_net::(); 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /rd-std/src/rule/any.rs: -------------------------------------------------------------------------------- 1 | use super::config::AnyMatcher; 2 | use super::matcher::{MatchContext, Matcher, MaybeAsync}; 3 | 4 | impl Matcher for AnyMatcher { 5 | fn match_rule(&self, _match_context: &MatchContext) -> MaybeAsync { 6 | true.into() 7 | } 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use rd_interface::{Context, IntoAddress}; 13 | 14 | use super::*; 15 | 16 | #[tokio::test] 17 | async fn test_any_matcher() { 18 | let matcher = AnyMatcher {}; 19 | let mut match_context = MatchContext::from_context_address( 20 | &Context::new(), 21 | &"127.0.0.1:26666".into_address().unwrap(), 22 | ) 23 | .unwrap(); 24 | assert!(matcher.match_rule(&mut match_context).await); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rd-std/src/rule/ipcidr.rs: -------------------------------------------------------------------------------- 1 | use super::config::{IpCidrMatcher, SrcIpCidrMatcher}; 2 | use super::matcher::{MatchContext, Matcher, MaybeAsync}; 3 | use smoltcp::wire::IpAddress; 4 | 5 | impl IpCidrMatcher { 6 | fn test(&self, address: impl Into) -> bool { 7 | let address: IpAddress = address.into(); 8 | self.ipcidr.iter().any(|i| i.0.contains_addr(&address)) 9 | } 10 | } 11 | 12 | impl Matcher for IpCidrMatcher { 13 | fn match_rule(&self, match_context: &MatchContext) -> MaybeAsync { 14 | match match_context.get_socket_addr() { 15 | Some(addr) => self.test(addr.ip()), 16 | _ => false, 17 | } 18 | .into() 19 | } 20 | } 21 | 22 | impl SrcIpCidrMatcher { 23 | fn test(&self, address: impl Into) -> bool { 24 | let address: IpAddress = address.into(); 25 | self.ipcidr.iter().any(|i| i.0.contains_addr(&address)) 26 | } 27 | } 28 | 29 | impl Matcher for SrcIpCidrMatcher { 30 | fn match_rule(&self, match_context: &MatchContext) -> MaybeAsync { 31 | match match_context.src_ip_addr() { 32 | Some(addr) => self.test(*addr), 33 | None => false, 34 | } 35 | .into() 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | #[tokio::test] 42 | async fn test_ipcidr() { 43 | use super::*; 44 | use crate::rule::config; 45 | use rd_interface::{Context, IntoAddress}; 46 | use smoltcp::wire::{IpAddress, IpCidr}; 47 | 48 | let matcher = IpCidrMatcher { 49 | ipcidr: vec![config::IpCidr(IpCidr::new( 50 | IpAddress::v4(192, 168, 1, 1), 51 | 24, 52 | ))] 53 | .into(), 54 | }; 55 | 56 | assert_eq!( 57 | matcher 58 | .match_rule( 59 | &MatchContext::from_context_address( 60 | &Context::new(), 61 | &"192.168.1.2:26666".into_address().unwrap() 62 | ) 63 | .unwrap() 64 | ) 65 | .await, 66 | true 67 | ); 68 | 69 | assert_eq!( 70 | matcher 71 | .match_rule( 72 | &MatchContext::from_context_address( 73 | &Context::new(), 74 | &"192.168.2.2:1234".into_address().unwrap() 75 | ) 76 | .unwrap() 77 | ) 78 | .await, 79 | false 80 | ); 81 | 82 | assert_eq!( 83 | matcher 84 | .match_rule( 85 | &MatchContext::from_context_address( 86 | &Context::new(), 87 | &"192.168.1.1:1234".into_address().unwrap() 88 | ) 89 | .unwrap() 90 | ) 91 | .await, 92 | true 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /rd-std/src/rule/matcher.rs: -------------------------------------------------------------------------------- 1 | use futures::{future::BoxFuture, Future, FutureExt}; 2 | use rd_interface::{ 3 | context::common_field::{DestDomain, DestSocketAddr, SrcSocketAddr}, 4 | Address, AddressDomain, Result, 5 | }; 6 | use std::{ 7 | net::{IpAddr, SocketAddr}, 8 | pin, task, 9 | }; 10 | 11 | pub(super) enum MaybeAsync { 12 | Sync { 13 | value: Option, 14 | }, 15 | #[allow(dead_code)] 16 | Async { 17 | future: BoxFuture<'static, T>, 18 | }, 19 | } 20 | 21 | impl From for MaybeAsync { 22 | fn from(value: T) -> Self { 23 | MaybeAsync::Sync { value: Some(value) } 24 | } 25 | } 26 | 27 | impl Future for MaybeAsync { 28 | type Output = T; 29 | 30 | fn poll(self: pin::Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { 31 | match self.get_mut() { 32 | MaybeAsync::Sync { value } => { 33 | task::Poll::Ready(value.take().expect("Don't poll twice on MaybeAsync")) 34 | } 35 | MaybeAsync::Async { future } => future.poll_unpin(cx), 36 | } 37 | } 38 | } 39 | 40 | pub(super) trait Matcher: Send + Sync { 41 | fn match_rule(&self, match_context: &MatchContext) -> MaybeAsync; 42 | } 43 | 44 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 45 | pub(super) struct MatchContext { 46 | address: Address, 47 | src_ip_addr: Option, 48 | dest_socket_addr: Option, 49 | dest_domain: Option, 50 | } 51 | 52 | impl MatchContext { 53 | pub fn from_context_address( 54 | ctx: &rd_interface::Context, 55 | addr: &Address, 56 | ) -> Result { 57 | Ok(MatchContext { 58 | address: addr.to_normalized(), 59 | src_ip_addr: ctx.get_common::()?.map(|v| v.0.ip()), 60 | dest_socket_addr: ctx.get_common::()?.map(|v| v.0), 61 | dest_domain: ctx.get_common::()?.map(|v| v.0), 62 | }) 63 | } 64 | pub fn address(&self) -> &Address { 65 | &self.address 66 | } 67 | pub fn src_ip_addr(&self) -> Option<&IpAddr> { 68 | self.src_ip_addr.as_ref() 69 | } 70 | pub fn dest_socket_addr(&self) -> Option<&SocketAddr> { 71 | self.dest_socket_addr.as_ref() 72 | } 73 | pub fn dest_domain(&self) -> Option<&AddressDomain> { 74 | self.dest_domain.as_ref() 75 | } 76 | pub fn get_domain(&self) -> Option<(&String, &u16)> { 77 | match self.address() { 78 | Address::Domain(d, p) => return Some((d, p)), 79 | Address::SocketAddr(_) => {} 80 | }; 81 | match self.dest_domain() { 82 | Some(AddressDomain { domain, port }) => return Some((domain, port)), 83 | None => {} 84 | }; 85 | 86 | None 87 | } 88 | pub fn get_socket_addr(&self) -> Option { 89 | match self.address() { 90 | Address::SocketAddr(addr) => return Some(*addr), 91 | Address::Domain(_, _) => match self.address.to_socket_addr() { 92 | Ok(addr) => return Some(addr), 93 | Err(_) => {} 94 | }, 95 | }; 96 | match self.dest_socket_addr() { 97 | Some(addr) => return Some(*addr), 98 | None => {} 99 | }; 100 | 101 | None 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /rd-std/src/sniffer.rs: -------------------------------------------------------------------------------- 1 | pub use dns_sniffer::DNSSnifferNet; 2 | pub use sni_sniffer::SNISnifferNet; 3 | 4 | use rd_interface::{ 5 | prelude::*, 6 | rd_config, 7 | registry::{Builder, NetRef}, 8 | Net, Registry, Result, 9 | }; 10 | 11 | mod dns_sniffer; 12 | mod service; 13 | mod sni_sniffer; 14 | 15 | #[rd_config] 16 | #[derive(Debug)] 17 | pub struct DNSNetConfig { 18 | #[serde(default)] 19 | net: NetRef, 20 | } 21 | 22 | impl Builder for DNSSnifferNet { 23 | const NAME: &'static str = "dns_sniffer"; 24 | type Config = DNSNetConfig; 25 | type Item = Self; 26 | 27 | fn build(config: Self::Config) -> Result { 28 | Ok(DNSSnifferNet::new(config.net.value_cloned())) 29 | } 30 | } 31 | 32 | #[rd_config] 33 | #[derive(Debug)] 34 | pub struct SNINetConfig { 35 | #[serde(default)] 36 | net: NetRef, 37 | /// Ports to sniff. 38 | /// If not set, only 443 port will be sniffed. 39 | #[serde(default)] 40 | ports: Option>, 41 | /// Force sniff domain. 42 | /// By default, only sniff connection to IP address. 43 | /// If set to true, will sniff all connection. 44 | #[serde(default)] 45 | force_sniff: bool, 46 | } 47 | 48 | impl Builder for SNISnifferNet { 49 | const NAME: &'static str = "sni_sniffer"; 50 | type Config = SNINetConfig; 51 | type Item = Self; 52 | 53 | fn build(config: Self::Config) -> Result { 54 | Ok(SNISnifferNet::new( 55 | config.net.value_cloned(), 56 | config.ports, 57 | config.force_sniff, 58 | )) 59 | } 60 | } 61 | 62 | pub fn init(registry: &mut Registry) -> Result<()> { 63 | registry.add_net::(); 64 | registry.add_net::(); 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /rd-std/src/sniffer/service.rs: -------------------------------------------------------------------------------- 1 | use std::{net::IpAddr, sync::Arc, time::Duration}; 2 | 3 | use lru_time_cache::LruCache; 4 | use parking_lot::Mutex; 5 | use trust_dns_proto::{ 6 | op::Message, 7 | rr::RData, 8 | serialize::binary::{BinDecodable, BinDecoder}, 9 | }; 10 | 11 | struct Inner { 12 | records: LruCache, 13 | cname_map: LruCache, 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct ReverseLookup { 18 | inner: Arc>, 19 | } 20 | 21 | impl ReverseLookup { 22 | pub fn new() -> ReverseLookup { 23 | ReverseLookup { 24 | inner: Arc::new(Mutex::new(Inner { 25 | records: LruCache::with_expiry_duration_and_capacity( 26 | Duration::from_secs(10 * 60), 27 | 128, 28 | ), 29 | cname_map: LruCache::with_expiry_duration_and_capacity( 30 | Duration::from_secs(10 * 60), 31 | 128, 32 | ), 33 | })), 34 | } 35 | } 36 | pub fn record_packet(&self, packet: &[u8]) { 37 | let mut decoder = BinDecoder::new(packet); 38 | let msg = match Message::read(&mut decoder) { 39 | Ok(msg) if msg.queries().len() == 1 => msg, 40 | _ => return, 41 | }; 42 | 43 | // It seems to be ok to assume that only one question is present 44 | // https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query/4083071#4083071 45 | let domain = msg.queries().first().unwrap().name().to_utf8(); 46 | let domain = domain.trim_end_matches('.'); 47 | 48 | let Inner { records, cname_map } = &mut *self.inner.lock(); 49 | for rdata in msg.answers().iter().flat_map(|i| i.data()) { 50 | match rdata { 51 | RData::A(addr) => { 52 | records.insert((*addr).into(), domain.to_string()); 53 | } 54 | RData::AAAA(addr) => { 55 | records.insert((*addr).into(), domain.to_string()); 56 | } 57 | RData::CNAME(cname) => { 58 | let cname = cname.to_utf8().trim_end_matches('.').to_string(); 59 | cname_map.insert(cname, domain.to_string()); 60 | } 61 | _ => {} 62 | } 63 | } 64 | } 65 | pub fn reverse_lookup(&self, addr: IpAddr) -> Option { 66 | let Inner { records, cname_map } = &mut *self.inner.lock(); 67 | match records.get(&addr) { 68 | Some(mut d) => { 69 | let mut limit = 16; 70 | while let Some(r) = cname_map.peek(d) { 71 | if r == d || limit == 0 { 72 | break; 73 | } 74 | d = r; 75 | limit -= 1; 76 | } 77 | Some(d.to_string()) 78 | } 79 | None => None, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /rd-std/src/socks5.rs: -------------------------------------------------------------------------------- 1 | pub use self::{client::Socks5Client, server::Socks5Server}; 2 | 3 | use rd_interface::{ 4 | prelude::*, 5 | registry::{Builder, NetRef}, 6 | Address, Net, Registry, Result, Server, 7 | }; 8 | 9 | mod client; 10 | mod common; 11 | mod server; 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | #[rd_config] 16 | #[derive(Debug)] 17 | pub struct Socks5NetConfig { 18 | server: Address, 19 | 20 | #[serde(default)] 21 | net: NetRef, 22 | } 23 | 24 | #[rd_config] 25 | #[derive(Debug)] 26 | pub struct Socks5ServerConfig { 27 | bind: Address, 28 | 29 | #[serde(default)] 30 | net: NetRef, 31 | #[serde(default)] 32 | listen: NetRef, 33 | } 34 | 35 | impl Builder for Socks5Client { 36 | const NAME: &'static str = "socks5"; 37 | type Config = Socks5NetConfig; 38 | type Item = Self; 39 | 40 | fn build(config: Self::Config) -> Result { 41 | Ok(Socks5Client::new(config.net.value_cloned(), config.server)) 42 | } 43 | } 44 | 45 | impl Builder for server::Socks5 { 46 | const NAME: &'static str = "socks5"; 47 | type Config = Socks5ServerConfig; 48 | type Item = Self; 49 | 50 | fn build(Self::Config { listen, net, bind }: Self::Config) -> Result { 51 | Ok(server::Socks5::new( 52 | listen.value_cloned(), 53 | net.value_cloned(), 54 | bind, 55 | )) 56 | } 57 | } 58 | 59 | pub fn init(registry: &mut Registry) -> Result<()> { 60 | registry.add_net::(); 61 | registry.add_server::(); 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /rd-std/src/socks5/common.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{Address as RDAddr, ReadBuf}; 2 | use socks5_protocol::{sync::FromIO, Address, Error}; 3 | use std::io::{self, ErrorKind, Read, Result, Write}; 4 | 5 | pub fn map_err(e: Error) -> rd_interface::Error { 6 | match e { 7 | Error::Io(io) => rd_interface::Error::IO(io), 8 | e => rd_interface::Error::Other(e.into()), 9 | } 10 | } 11 | 12 | pub fn parse_udp(buf: &mut ReadBuf) -> Result { 13 | let filled = buf.filled_mut(); 14 | let mut cursor = std::io::Cursor::new(filled); 15 | let mut header = [0u8; 3]; 16 | cursor.read_exact(&mut header)?; 17 | let addr = match header[0..3] { 18 | // TODO: support fragment sequence or at least give another error 19 | [0x00, 0x00, 0x00] => Address::read_from(&mut cursor).map_err(map_err)?, 20 | _ => { 21 | return Err(io::Error::new( 22 | ErrorKind::InvalidData, 23 | format!( 24 | "server response wrong RSV {} RSV {} FRAG {}", 25 | header[0], header[1], header[2] 26 | ), 27 | )) 28 | } 29 | }; 30 | 31 | let pos = cursor.position() as usize; 32 | cursor.get_mut().copy_within(pos.., 0); 33 | 34 | buf.set_filled(buf.filled().len() - pos); 35 | 36 | Ok(sa2ra(addr)) 37 | } 38 | 39 | pub fn pack_udp(addr: RDAddr, buf: &[u8], vec: &mut Vec) -> Result<()> { 40 | vec.clear(); 41 | let mut cursor = std::io::Cursor::new(vec); 42 | cursor.write_all(&[0x00, 0x00, 0x00])?; 43 | ra2sa(addr).write_to(&mut cursor).map_err(map_err)?; 44 | cursor.write_all(buf)?; 45 | 46 | Ok(()) 47 | } 48 | 49 | pub fn sa2ra(addr: socks5_protocol::Address) -> rd_interface::Address { 50 | match addr { 51 | socks5_protocol::Address::Domain(d, p) => rd_interface::Address::Domain(d, p), 52 | socks5_protocol::Address::SocketAddr(s) => rd_interface::Address::SocketAddr(s), 53 | } 54 | } 55 | pub fn ra2sa(addr: rd_interface::Address) -> socks5_protocol::Address { 56 | match addr { 57 | rd_interface::Address::Domain(d, p) => socks5_protocol::Address::Domain(d, p), 58 | rd_interface::Address::SocketAddr(s) => socks5_protocol::Address::SocketAddr(s), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rd-std/src/socks5/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::tests::{ 3 | assert_echo, assert_echo_udp, get_registry, spawn_echo_server, spawn_echo_server_udp, TestNet, 4 | }; 5 | use rd_interface::IntoAddress; 6 | use rd_interface::{IServer, IntoDyn}; 7 | use std::time::Duration; 8 | use tokio::time::sleep; 9 | 10 | #[test] 11 | fn test_socks5_smoke() { 12 | let mut registry = get_registry(); 13 | super::init(&mut registry).unwrap(); 14 | } 15 | 16 | #[tokio::test] 17 | async fn test_socks5_server_client() { 18 | let local = TestNet::new().into_dyn(); 19 | spawn_echo_server(&local, "127.0.0.1:26666").await; 20 | spawn_echo_server_udp(&local, "127.0.0.1:26666").await; 21 | 22 | let server = server::Socks5::new( 23 | local.clone(), 24 | local.clone(), 25 | "127.0.0.1:16666".into_address().unwrap(), 26 | ); 27 | tokio::spawn(async move { server.start().await }); 28 | 29 | sleep(Duration::from_secs(1)).await; 30 | 31 | let client = 32 | client::Socks5Client::new(local, "127.0.0.1:16666".into_address().unwrap()).into_dyn(); 33 | 34 | assert_echo(&client, "127.0.0.1:26666").await; 35 | assert_echo_udp(&client, "127.0.0.1:26666").await; 36 | } 37 | -------------------------------------------------------------------------------- /rd-std/src/tests.rs: -------------------------------------------------------------------------------- 1 | pub use self::net::TestNet; 2 | use crate::builtin; 3 | use rd_interface::{Context, IntoAddress, Net, ReadBuf, Registry}; 4 | use std::time::Duration; 5 | use tokio::{ 6 | io::{self, AsyncReadExt, AsyncWriteExt}, 7 | task::yield_now, 8 | time::timeout, 9 | }; 10 | 11 | const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); 12 | 13 | mod channel; 14 | mod net; 15 | 16 | pub fn get_registry() -> Registry { 17 | let mut registry = Registry::new(); 18 | builtin::init(&mut registry).unwrap(); 19 | registry 20 | } 21 | 22 | pub async fn spawn_echo_server_udp(net: &Net, addr: impl IntoAddress) { 23 | let mut udp = net 24 | .udp_bind(&mut Context::new(), &addr.into_address().unwrap()) 25 | .await 26 | .unwrap(); 27 | tokio::spawn(async move { 28 | let vec = &mut vec![0; 4096]; 29 | loop { 30 | let mut buf = ReadBuf::new(vec); 31 | let addr = udp.recv_from(&mut buf).await.unwrap(); 32 | udp.send_to(buf.filled(), &addr.into()).await.unwrap(); 33 | } 34 | }); 35 | yield_now().await; 36 | } 37 | 38 | pub async fn assert_echo_udp(net: &Net, addr: impl IntoAddress) { 39 | let target_addr = addr.into_address().unwrap(); 40 | let mut socket = net 41 | .udp_bind(&mut Context::new(), &"0.0.0.0:0".parse().unwrap()) 42 | .await 43 | .unwrap(); 44 | 45 | socket.send_to(b"hello", &target_addr).await.unwrap(); 46 | let buf = &mut vec![0; 4096]; 47 | let mut buf = ReadBuf::new(buf); 48 | timeout(DEFAULT_TIMEOUT, socket.recv_from(&mut buf)) 49 | .await 50 | .unwrap() 51 | .unwrap(); 52 | 53 | assert_eq!(buf.filled(), b"hello"); 54 | } 55 | 56 | pub async fn spawn_echo_server(net: &Net, addr: impl IntoAddress) { 57 | let listener = net 58 | .tcp_bind(&mut Context::new(), &addr.into_address().unwrap()) 59 | .await 60 | .unwrap(); 61 | tokio::spawn(async move { 62 | loop { 63 | let (tcp, _) = listener.accept().await.unwrap(); 64 | tokio::spawn(async move { 65 | let (mut rx, mut tx) = io::split(tcp); 66 | io::copy(&mut rx, &mut tx).await.unwrap(); 67 | }); 68 | } 69 | }); 70 | yield_now().await; 71 | } 72 | 73 | pub async fn assert_echo(net: &Net, addr: impl IntoAddress) { 74 | const BUF: &[u8] = b"asdfasdfasdfasj12312313123"; 75 | let mut tcp = net 76 | .tcp_connect(&mut Context::new(), &addr.into_address().unwrap()) 77 | .await 78 | .unwrap(); 79 | tcp.write_all(BUF).await.unwrap(); 80 | 81 | let mut buf = [0u8; BUF.len()]; 82 | timeout(DEFAULT_TIMEOUT, tcp.read_exact(&mut buf)) 83 | .await 84 | .unwrap() 85 | .unwrap(); 86 | 87 | assert_eq!(buf, BUF); 88 | } 89 | 90 | #[derive(Default, Debug)] 91 | pub struct ProviderCapability { 92 | pub tcp_connect: bool, 93 | pub tcp_bind: bool, 94 | pub udp_bind: bool, 95 | pub lookup_host: bool, 96 | } 97 | 98 | pub fn assert_net_provider(net: &Net, capability: ProviderCapability) { 99 | assert_eq!(net.provide_tcp_connect().is_some(), capability.tcp_connect); 100 | assert_eq!(net.provide_tcp_bind().is_some(), capability.tcp_bind); 101 | assert_eq!(net.provide_udp_bind().is_some(), capability.udp_bind); 102 | assert_eq!(net.provide_lookup_host().is_some(), capability.lookup_host); 103 | } 104 | -------------------------------------------------------------------------------- /rd-std/src/tests/channel.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | task::{Context, Poll}, 4 | }; 5 | 6 | use futures::{Sink, SinkExt, Stream}; 7 | use pin_project_lite::pin_project; 8 | use tokio::sync::mpsc::{channel, Receiver}; 9 | use tokio_util::sync::{PollSendError, PollSender}; 10 | 11 | pin_project! { 12 | #[derive(Debug)] 13 | pub struct Channel { 14 | buf: Option, 15 | sender: PollSender, 16 | receiver: Receiver, 17 | } 18 | } 19 | 20 | impl Channel { 21 | // return a pair of channel 22 | pub fn new() -> (Channel, Channel) { 23 | let (sender1, receiver1) = channel(10); 24 | let (sender2, receiver2) = channel(10); 25 | let sender1 = PollSender::new(sender1); 26 | let sender2 = PollSender::new(sender2); 27 | ( 28 | Channel { 29 | buf: None, 30 | sender: sender1, 31 | receiver: receiver2, 32 | }, 33 | Channel { 34 | buf: None, 35 | sender: sender2, 36 | receiver: receiver1, 37 | }, 38 | ) 39 | } 40 | pub fn is_closed(&self) -> bool { 41 | self.sender.is_closed() 42 | } 43 | } 44 | 45 | impl Stream for Channel { 46 | type Item = T; 47 | 48 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 49 | let this = self.project(); 50 | this.receiver.poll_recv(cx) 51 | } 52 | } 53 | 54 | impl Sink for Channel { 55 | type Error = PollSendError; 56 | 57 | fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 58 | let this = self.project(); 59 | this.sender.poll_ready_unpin(cx) 60 | } 61 | 62 | fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { 63 | let this = self.project(); 64 | this.sender.start_send_unpin(item) 65 | } 66 | 67 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 68 | let this = self.project(); 69 | this.sender.poll_flush_unpin(cx) 70 | } 71 | 72 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 73 | let this = self.project(); 74 | this.sender.poll_close_unpin(cx) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rd-std/src/tls.rs: -------------------------------------------------------------------------------- 1 | use backend::*; 2 | use rd_interface::{ 3 | async_trait, config::NetRef, prelude::*, rd_config, registry::Builder, Address, INet, Net, 4 | Registry, Result, TcpStream, 5 | }; 6 | 7 | #[cfg(feature = "rustls")] 8 | #[path = "tls/rustls.rs"] 9 | mod backend; 10 | 11 | #[cfg(feature = "openssl")] 12 | #[path = "tls/openssl.rs"] 13 | mod backend; 14 | 15 | #[cfg(feature = "native-tls")] 16 | #[path = "tls/native-tls.rs"] 17 | mod backend; 18 | 19 | #[derive(Clone)] 20 | pub(crate) struct TlsConnectorConfig { 21 | pub skip_cert_verify: bool, 22 | } 23 | 24 | #[rd_config] 25 | pub struct TlsNetConfig { 26 | /// Dangerous, but can be used to skip certificate verification. 27 | #[serde(default)] 28 | pub skip_cert_verify: bool, 29 | 30 | /// Override domain with SNI 31 | #[serde(default)] 32 | pub sni: Option, 33 | 34 | #[serde(default)] 35 | pub net: NetRef, 36 | } 37 | 38 | pub struct TlsNet { 39 | connector: TlsConnector, 40 | sni: Option, 41 | net: Net, 42 | } 43 | 44 | #[async_trait] 45 | impl rd_interface::TcpConnect for TlsNet { 46 | async fn tcp_connect( 47 | &self, 48 | ctx: &mut rd_interface::Context, 49 | addr: &Address, 50 | ) -> Result { 51 | let stream = self.net.tcp_connect(ctx, addr).await?; 52 | let tls_stream = match &self.sni { 53 | Some(d) => self.connector.connect(d, stream).await?, 54 | None => self.connector.connect(&addr.host(), stream).await?, 55 | }; 56 | 57 | Ok(TcpStream::from(tls_stream)) 58 | } 59 | } 60 | 61 | impl INet for TlsNet { 62 | fn provide_tcp_connect(&self) -> Option<&dyn rd_interface::TcpConnect> { 63 | Some(self) 64 | } 65 | } 66 | 67 | impl Builder for TlsNet { 68 | const NAME: &'static str = "tls"; 69 | 70 | type Config = TlsNetConfig; 71 | 72 | type Item = TlsNet; 73 | 74 | fn build(cfg: Self::Config) -> Result { 75 | Ok(TlsNet { 76 | connector: TlsConnector::new(TlsConnectorConfig { 77 | skip_cert_verify: cfg.skip_cert_verify, 78 | })?, 79 | sni: cfg.sni, 80 | net: cfg.net.value_cloned(), 81 | }) 82 | } 83 | } 84 | 85 | pub fn init(registry: &mut Registry) -> Result<()> { 86 | registry.add_net::(); 87 | Ok(()) 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use crate::tests::{assert_net_provider, ProviderCapability, TestNet}; 93 | use rd_interface::IntoDyn; 94 | 95 | use super::*; 96 | 97 | #[test] 98 | fn test_provider() { 99 | let net = TestNet::new().into_dyn(); 100 | 101 | let tls = TlsNet { 102 | connector: TlsConnector::new(TlsConnectorConfig { 103 | skip_cert_verify: false, 104 | }) 105 | .unwrap(), 106 | sni: None, 107 | net, 108 | } 109 | .into_dyn(); 110 | 111 | assert_net_provider( 112 | &tls, 113 | ProviderCapability { 114 | tcp_connect: true, 115 | ..Default::default() 116 | }, 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /rd-std/src/tls/native-tls.rs: -------------------------------------------------------------------------------- 1 | use super::TlsConnectorConfig; 2 | use native_tls_crate as _; 3 | use rd_interface::{error::map_other, AsyncRead, AsyncWrite, Result}; 4 | use tokio_native_tls::native_tls; 5 | 6 | pub use tokio_native_tls::TlsStream; 7 | 8 | pub struct TlsConnector { 9 | connector: tokio_native_tls::TlsConnector, 10 | } 11 | 12 | impl TlsConnector { 13 | pub(crate) fn new(config: TlsConnectorConfig) -> Result { 14 | let mut builder = native_tls::TlsConnector::builder(); 15 | if config.skip_cert_verify { 16 | builder.danger_accept_invalid_certs(true); 17 | } 18 | let connector = tokio_native_tls::TlsConnector::from(builder.build().map_err(map_other)?); 19 | 20 | Ok(TlsConnector { connector }) 21 | } 22 | pub async fn connect(&self, domain: &str, stream: IO) -> Result> 23 | where 24 | IO: AsyncRead + AsyncWrite + Unpin, 25 | { 26 | let stream = self 27 | .connector 28 | .connect(domain, stream) 29 | .await 30 | .map_err(map_other)?; 31 | Ok(stream) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rd-std/src/tls/openssl.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | use super::TlsConnectorConfig; 4 | use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; 5 | use openssl_crate as openssl; 6 | use rd_interface::{error::map_other, AsyncRead, AsyncWrite, Result}; 7 | 8 | pub use tokio_openssl::SslStream as TlsStream; 9 | 10 | pub struct TlsConnector { 11 | connector: SslConnector, 12 | } 13 | 14 | impl TlsConnector { 15 | pub(crate) fn new(config: TlsConnectorConfig) -> Result { 16 | let mut builder = SslConnector::builder(SslMethod::tls()).map_err(map_other)?; 17 | 18 | if config.skip_cert_verify { 19 | builder.set_verify(SslVerifyMode::NONE); 20 | } 21 | 22 | Ok(TlsConnector { 23 | connector: builder.build(), 24 | }) 25 | } 26 | 27 | pub async fn connect(&self, domain: &str, stream: IO) -> Result> 28 | where 29 | IO: AsyncRead + AsyncWrite + Unpin, 30 | { 31 | let ssl = self 32 | .connector 33 | .configure() 34 | .map_err(map_other)? 35 | .into_ssl(domain) 36 | .map_err(map_other)?; 37 | 38 | let mut stream = TlsStream::new(ssl, stream).map_err(map_other)?; 39 | 40 | Pin::new(&mut stream).connect().await.map_err(map_other)?; 41 | 42 | Ok(stream) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rd-std/src/transparent.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | mod origin_addr; 3 | #[cfg(target_os = "linux")] 4 | mod redir; 5 | #[cfg(target_os = "linux")] 6 | mod socket; 7 | #[cfg(target_os = "linux")] 8 | mod tproxy; 9 | 10 | use rd_interface::{Registry, Result}; 11 | #[cfg(target_os = "linux")] 12 | use redir::RedirServer; 13 | #[cfg(target_os = "linux")] 14 | use tproxy::TProxyServer; 15 | 16 | pub fn init(_registry: &mut Registry) -> Result<()> { 17 | #[cfg(target_os = "linux")] 18 | _registry.add_server::(); 19 | #[cfg(target_os = "linux")] 20 | _registry.add_server::(); 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /rd-std/src/transparent/origin_addr.rs: -------------------------------------------------------------------------------- 1 | use std::{io, net::SocketAddr}; 2 | 3 | pub trait OriginAddrExt { 4 | fn origin_addr(&self) -> io::Result; 5 | } 6 | 7 | use std::os::unix::prelude::AsRawFd; 8 | 9 | impl OriginAddrExt for T { 10 | fn origin_addr(&self) -> io::Result { 11 | use socket2::SockAddr; 12 | 13 | let fd = self.as_raw_fd(); 14 | 15 | unsafe { 16 | let (_, origin_addr) = SockAddr::try_init(|origin_addr, origin_addr_len| { 17 | let ret = if libc::getsockopt( 18 | fd, 19 | libc::SOL_IP, 20 | libc::SO_ORIGINAL_DST, 21 | origin_addr as *mut _, 22 | origin_addr_len, 23 | ) == 0 24 | { 25 | 0 26 | } else { 27 | libc::getsockopt( 28 | fd, 29 | libc::SOL_IPV6, 30 | libc::IP6T_SO_ORIGINAL_DST, 31 | origin_addr as *mut _, 32 | origin_addr_len, 33 | ) 34 | }; 35 | if ret != 0 { 36 | let err = io::Error::last_os_error(); 37 | return Err(err); 38 | } 39 | Ok(()) 40 | })?; 41 | Ok(origin_addr.as_socket().expect("SocketAddr")) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rd-std/src/transparent/redir.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use super::origin_addr::OriginAddrExt; 4 | use crate::{builtin::local::CompatTcp, ContextExt}; 5 | use rd_derive::rd_config; 6 | use rd_interface::{ 7 | async_trait, config::NetRef, registry::Builder, schemars, Address, Context, IServer, 8 | IntoAddress, IntoDyn, Net, Result, Server, 9 | }; 10 | use tokio::net::{TcpListener, TcpStream}; 11 | use tracing::instrument; 12 | 13 | #[rd_config] 14 | #[derive(Debug)] 15 | pub struct RedirServerConfig { 16 | bind: Address, 17 | #[serde(default)] 18 | net: NetRef, 19 | } 20 | 21 | pub struct RedirServer { 22 | bind: Address, 23 | net: Net, 24 | } 25 | 26 | #[async_trait] 27 | impl IServer for RedirServer { 28 | async fn start(&self) -> Result<()> { 29 | let listener = TcpListener::bind(&self.bind.to_string()).await?; 30 | self.serve_listener(listener).await 31 | } 32 | } 33 | 34 | impl RedirServer { 35 | pub fn new(bind: Address, net: Net) -> Self { 36 | RedirServer { bind, net } 37 | } 38 | 39 | pub async fn serve_listener(&self, listener: TcpListener) -> Result<()> { 40 | loop { 41 | let (socket, addr) = listener.accept().await?; 42 | let net = self.net.clone(); 43 | let _ = tokio::spawn(async move { 44 | if let Err(e) = Self::serve_connection(net, socket, addr).await { 45 | tracing::error!("Error when serve_connection: {:?}", e); 46 | } 47 | }); 48 | } 49 | } 50 | 51 | #[instrument(err, skip(net, socket))] 52 | async fn serve_connection(net: Net, socket: TcpStream, addr: SocketAddr) -> Result<()> { 53 | let target = socket.origin_addr()?; 54 | 55 | let ctx = &mut Context::from_socketaddr(addr); 56 | let target_tcp = net.tcp_connect(ctx, &target.into_address()?).await?; 57 | let socket = CompatTcp(socket).into_dyn(); 58 | 59 | ctx.connect_tcp(socket, target_tcp).await?; 60 | 61 | Ok(()) 62 | } 63 | } 64 | 65 | impl Builder for RedirServer { 66 | const NAME: &'static str = "redir"; 67 | type Config = RedirServerConfig; 68 | type Item = Self; 69 | 70 | fn build(Self::Config { bind, net }: Self::Config) -> Result { 71 | Ok(RedirServer::new(bind, net.value_cloned())) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rd-std/src/util/drop_abort.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ops::{Deref, DerefMut}, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures::Future; 8 | use tokio::task::{JoinError, JoinHandle}; 9 | 10 | pub struct DropAbort(JoinHandle); 11 | 12 | impl Drop for DropAbort { 13 | fn drop(&mut self) { 14 | self.0.abort(); 15 | } 16 | } 17 | 18 | impl Deref for DropAbort { 19 | type Target = JoinHandle; 20 | 21 | fn deref(&self) -> &Self::Target { 22 | &self.0 23 | } 24 | } 25 | 26 | impl DerefMut for DropAbort { 27 | fn deref_mut(&mut self) -> &mut Self::Target { 28 | &mut self.0 29 | } 30 | } 31 | 32 | impl DropAbort { 33 | pub fn new(handle: JoinHandle) -> Self { 34 | DropAbort(handle) 35 | } 36 | } 37 | 38 | impl Future for DropAbort { 39 | type Output = Result; 40 | 41 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 42 | Pin::new(&mut self.0).poll(cx) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rd-std/src/util/forward_udp/connection.rs: -------------------------------------------------------------------------------- 1 | use std::{io, net::SocketAddr}; 2 | 3 | use crate::ContextExt; 4 | 5 | use super::{send_back::BackChannel, UdpPacket}; 6 | use rd_interface::{Address, Context, IntoDyn, Net, Result}; 7 | use tokio::{ 8 | sync::mpsc::{channel, Sender}, 9 | task::JoinHandle, 10 | }; 11 | 12 | pub(super) struct UdpConnection { 13 | handle: JoinHandle>, 14 | send_udp: Sender<(Vec, SocketAddr)>, 15 | } 16 | 17 | impl UdpConnection { 18 | pub(super) fn new( 19 | net: Net, 20 | bind_from: SocketAddr, 21 | bind_addr: Address, 22 | send_back: Sender, 23 | channel_size: usize, 24 | ) -> UdpConnection { 25 | let (send_udp, rx) = channel(channel_size); 26 | let back_channel = BackChannel::new(bind_from, send_back, rx).into_dyn(); 27 | let fut = async move { 28 | let mut ctx = Context::from_socketaddr(bind_from); 29 | let udp = net.udp_bind(&mut ctx, &bind_addr).await?; 30 | 31 | ctx.connect_udp(back_channel, udp).await?; 32 | 33 | Ok(()) 34 | }; 35 | 36 | UdpConnection { 37 | handle: tokio::spawn(fut), 38 | send_udp, 39 | } 40 | } 41 | pub(super) fn send(&mut self, packet: (Vec, SocketAddr)) -> Result<()> { 42 | self.send_udp 43 | .try_send(packet) 44 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e).into()) 45 | } 46 | } 47 | 48 | impl Drop for UdpConnection { 49 | fn drop(&mut self) { 50 | self.handle.abort(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rd-std/src/util/forward_udp/send_back.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::SocketAddr, 4 | task::{self, Poll}, 5 | }; 6 | 7 | use super::UdpPacket; 8 | use futures::{ready, SinkExt}; 9 | use rd_interface::{Address, IUdpChannel}; 10 | use tokio::sync::mpsc::{Receiver, Sender}; 11 | use tokio_util::sync::PollSender; 12 | 13 | pub(super) struct BackChannel { 14 | to: SocketAddr, 15 | sender: PollSender, 16 | receiver: Receiver<(Vec, SocketAddr)>, 17 | flushing: bool, 18 | } 19 | 20 | impl BackChannel { 21 | pub(super) fn new( 22 | to: SocketAddr, 23 | sender: Sender, 24 | receiver: Receiver<(Vec, SocketAddr)>, 25 | ) -> BackChannel { 26 | BackChannel { 27 | to, 28 | sender: PollSender::new(sender), 29 | receiver, 30 | flushing: false, 31 | } 32 | } 33 | } 34 | 35 | impl IUdpChannel for BackChannel { 36 | fn poll_send_to( 37 | &mut self, 38 | cx: &mut task::Context<'_>, 39 | buf: &mut rd_interface::ReadBuf, 40 | ) -> Poll> { 41 | let (item, addr) = match ready!(self.receiver.poll_recv(cx)) { 42 | Some(item) => item, 43 | None => { 44 | return Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "channel closed"))) 45 | } 46 | }; 47 | 48 | let to_copy = item.len().min(buf.remaining()); 49 | buf.initialize_unfilled_to(to_copy) 50 | .copy_from_slice(&item[..to_copy]); 51 | buf.advance(to_copy); 52 | 53 | Poll::Ready(Ok(addr.into())) 54 | } 55 | 56 | fn poll_recv_from( 57 | &mut self, 58 | cx: &mut task::Context<'_>, 59 | buf: &[u8], 60 | target: &SocketAddr, 61 | ) -> Poll> { 62 | loop { 63 | if self.flushing { 64 | ready!(self.sender.poll_flush_unpin(cx)) 65 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 66 | self.flushing = false; 67 | return Poll::Ready(Ok(buf.len())); 68 | } 69 | 70 | ready!(self.sender.poll_ready_unpin(cx)) 71 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 72 | 73 | let to = self.to; 74 | 75 | self.sender 76 | .start_send_unpin(UdpPacket { 77 | from: *target, 78 | to, 79 | data: buf.to_vec(), 80 | }) 81 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 82 | self.flushing = true; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rd-std/src/util/net.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{INet, Net}; 2 | 3 | /// A no-op Net returns [`Error::NotImplemented`](crate::Error::NotImplemented) for every method. 4 | pub struct NotImplementedNet; 5 | 6 | impl INet for NotImplementedNet {} 7 | 8 | /// A new Net calls [`tcp_connect()`](crate::INet::tcp_connect()), [`tcp_bind()`](crate::INet::tcp_bind()), [`udp_bind()`](crate::INet::udp_bind()) from different Net. 9 | pub struct CombineNet { 10 | pub tcp_connect: Net, 11 | pub tcp_bind: Net, 12 | pub udp_bind: Net, 13 | pub lookup_host: Net, 14 | } 15 | 16 | impl INet for CombineNet { 17 | fn provide_tcp_connect(&self) -> Option<&dyn rd_interface::TcpConnect> { 18 | self.tcp_connect.provide_tcp_connect() 19 | } 20 | 21 | fn provide_tcp_bind(&self) -> Option<&dyn rd_interface::TcpBind> { 22 | self.tcp_bind.provide_tcp_bind() 23 | } 24 | 25 | fn provide_udp_bind(&self) -> Option<&dyn rd_interface::UdpBind> { 26 | self.udp_bind.provide_udp_bind() 27 | } 28 | 29 | fn provide_lookup_host(&self) -> Option<&dyn rd_interface::LookupHost> { 30 | self.lookup_host.provide_lookup_host() 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use rd_interface::IntoDyn; 37 | use tokio::task::yield_now; 38 | 39 | use crate::tests::{ 40 | assert_echo, assert_echo_udp, assert_net_provider, spawn_echo_server, 41 | spawn_echo_server_udp, ProviderCapability, TestNet, 42 | }; 43 | 44 | use super::*; 45 | 46 | #[test] 47 | fn test_provider() { 48 | let tcp_connect = TestNet::new().into_dyn(); 49 | let tcp_bind = TestNet::new().into_dyn(); 50 | let udp_bind = TestNet::new().into_dyn(); 51 | let lookup_host = TestNet::new().into_dyn(); 52 | 53 | let net = CombineNet { 54 | tcp_connect, 55 | tcp_bind, 56 | udp_bind, 57 | lookup_host, 58 | } 59 | .into_dyn(); 60 | 61 | assert_net_provider( 62 | &net, 63 | ProviderCapability { 64 | tcp_connect: true, 65 | tcp_bind: true, 66 | udp_bind: true, 67 | lookup_host: true, 68 | }, 69 | ); 70 | } 71 | 72 | #[tokio::test] 73 | async fn test_combine_net() { 74 | let tcp_connect = TestNet::new().into_dyn(); 75 | let tcp_bind = TestNet::new().into_dyn(); 76 | let udp_bind = TestNet::new().into_dyn(); 77 | let lookup_host = TestNet::new().into_dyn(); 78 | let tcp_bind2 = tcp_bind.clone(); 79 | spawn_echo_server(&tcp_connect, "127.0.0.1:12345").await; 80 | spawn_echo_server_udp(&udp_bind, "127.0.0.1:12346").await; 81 | 82 | yield_now().await; 83 | 84 | let net = CombineNet { 85 | tcp_connect, 86 | tcp_bind, 87 | udp_bind, 88 | lookup_host, 89 | } 90 | .into_dyn(); 91 | 92 | assert_echo(&net, "127.0.0.1:12345").await; 93 | assert_echo_udp(&net, "127.0.0.1:12346").await; 94 | 95 | spawn_echo_server(&net, "127.0.0.1:12346").await; 96 | yield_now().await; 97 | assert_echo(&tcp_bind2, "127.0.0.1:12346").await; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /rd-std/src/util/poll_future.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | mem::replace, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | pub enum PollFuture { 9 | Empty, 10 | Ready(T), 11 | Future(Pin + Send>>), 12 | } 13 | 14 | impl PollFuture { 15 | pub fn new(fut: impl Future + Send + 'static) -> Self { 16 | PollFuture::Future(Box::pin(fut)) 17 | } 18 | pub fn ready(val: T) -> Self { 19 | PollFuture::Ready(val) 20 | } 21 | pub fn poll(&mut self, cx: &mut Context<'_>) -> Poll { 22 | match self { 23 | PollFuture::Empty => panic!("PollFuture::poll called on empty future"), 24 | PollFuture::Ready(_) => { 25 | let val = replace(self, PollFuture::Empty); 26 | Poll::Ready(match val { 27 | PollFuture::Ready(val) => val, 28 | _ => unreachable!(), 29 | }) 30 | } 31 | PollFuture::Future(fut) => { 32 | let val = futures::ready!(fut.as_mut().poll(cx)); 33 | *self = PollFuture::Empty; 34 | Poll::Ready(val) 35 | } 36 | } 37 | } 38 | pub fn is_ready(&self) -> bool { 39 | match self { 40 | PollFuture::Empty => true, 41 | PollFuture::Ready(_) => true, 42 | PollFuture::Future(_) => false, 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use tokio::sync::mpsc; 50 | 51 | use super::*; 52 | 53 | #[test] 54 | fn test_poll_future() { 55 | let (tx, mut rx) = mpsc::unbounded_channel::<()>(); 56 | let mut fut = PollFuture::new(async move { 57 | rx.recv().await; 58 | 1 59 | }); 60 | 61 | assert!(!fut.is_ready()); 62 | assert_eq!( 63 | fut.poll(&mut Context::from_waker(futures::task::noop_waker_ref())), 64 | Poll::Pending 65 | ); 66 | assert!(!fut.is_ready()); 67 | tx.send(()).unwrap(); 68 | assert_eq!( 69 | fut.poll(&mut Context::from_waker(futures::task::noop_waker_ref())), 70 | Poll::Ready(1) 71 | ); 72 | assert!(fut.is_ready()); 73 | } 74 | 75 | #[test] 76 | fn test_poll_future_ready() { 77 | let mut fut = PollFuture::ready(1); 78 | assert!(fut.is_ready()); 79 | assert_eq!( 80 | fut.poll(&mut Context::from_waker(futures::task::noop_waker_ref())), 81 | Poll::Ready(1) 82 | ); 83 | assert!(fut.is_ready()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["clippy", "rustfmt"] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /src/api_server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::Result; 4 | use rabbit_digger::RabbitDigger; 5 | 6 | use crate::config::ConfigManager; 7 | 8 | mod handlers; 9 | mod routes; 10 | 11 | pub struct ApiServer { 12 | pub rabbit_digger: RabbitDigger, 13 | pub config_manager: ConfigManager, 14 | pub access_token: Option, 15 | pub web_ui: Option, 16 | } 17 | 18 | impl ApiServer { 19 | pub async fn run(self, bind: &str) -> Result { 20 | let app = self.routes().await?; 21 | 22 | let server = axum::Server::bind(&bind.parse()?).serve(app.into_make_service()); 23 | let local_addr = server.local_addr(); 24 | tokio::spawn(server); 25 | 26 | Ok(local_addr) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/config/importer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::{config::Import, storage::Storage}; 4 | use anyhow::{anyhow, Result}; 5 | use once_cell::sync::OnceCell; 6 | use rabbit_digger::config::Config; 7 | use rd_interface::{ 8 | async_trait, 9 | registry::{Builder, Resolver}, 10 | }; 11 | 12 | mod clash; 13 | mod merge; 14 | #[cfg(feature = "rhai")] 15 | mod rhai; 16 | 17 | type Registry = BTreeMap<&'static str, Resolver>; 18 | 19 | pub fn get_importer_registry() -> &'static Registry { 20 | static REGISTRY: OnceCell = OnceCell::new(); 21 | REGISTRY.get_or_init(|| { 22 | let mut registry = BTreeMap::new(); 23 | 24 | fn add_importer>(r: &mut Registry) { 25 | r.insert(N::NAME, Resolver::new::()); 26 | } 27 | add_importer::(&mut registry); 28 | add_importer::(&mut registry); 29 | #[cfg(feature = "rhai")] 30 | add_importer::(&mut registry); 31 | 32 | registry 33 | }) 34 | } 35 | 36 | pub fn get_importer(import: &Import) -> Result> { 37 | let registry = get_importer_registry(); 38 | let resolver = registry 39 | .get(&import.format.as_ref()) 40 | .ok_or_else(|| anyhow!("Importer not found: {}", import.format))?; 41 | 42 | Ok(resolver.build( 43 | &|_, _| Err(rd_interface::Error::NotImplemented), 44 | &mut import.opt.clone(), 45 | )?) 46 | } 47 | 48 | #[async_trait] 49 | pub trait Importer: Send + Sync { 50 | async fn process( 51 | &mut self, 52 | config: &mut Config, 53 | content: &str, 54 | cache: &dyn Storage, 55 | ) -> Result<()>; 56 | } 57 | pub type BoxImporter = Box; 58 | -------------------------------------------------------------------------------- /src/config/importer/merge.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rabbit_digger::Config; 3 | use rd_interface::{async_trait, config::EmptyConfig, registry::Builder, IntoDyn}; 4 | 5 | use crate::storage::Storage; 6 | 7 | use super::{BoxImporter, Importer}; 8 | 9 | #[derive(Debug)] 10 | pub struct Merge; 11 | 12 | #[async_trait] 13 | impl Importer for Merge { 14 | async fn process( 15 | &mut self, 16 | config: &mut Config, 17 | content: &str, 18 | _cache: &dyn Storage, 19 | ) -> Result<()> { 20 | let other_content: Config = serde_yaml::from_str(content)?; 21 | config.merge(other_content); 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl Builder for Merge { 27 | const NAME: &'static str = "merge"; 28 | 29 | type Config = EmptyConfig; 30 | 31 | type Item = Merge; 32 | 33 | fn build(_config: Self::Config) -> rd_interface::Result { 34 | Ok(Merge) 35 | } 36 | } 37 | 38 | impl IntoDyn for Merge { 39 | fn into_dyn(self) -> BoxImporter { 40 | Box::new(self) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/config/importer/rhai.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use rabbit_digger::Config; 3 | use rd_interface::{async_trait, prelude::*, rd_config, registry::Builder, IntoDyn}; 4 | use rhai::{ 5 | serde::{from_dynamic, to_dynamic}, 6 | Engine, Scope, 7 | }; 8 | 9 | use crate::storage::Storage; 10 | 11 | use super::{BoxImporter, Importer}; 12 | 13 | #[rd_config] 14 | #[derive(Debug)] 15 | pub struct Rhai {} 16 | 17 | #[async_trait] 18 | impl Importer for Rhai { 19 | async fn process( 20 | &mut self, 21 | config: &mut Config, 22 | content: &str, 23 | _cache: &dyn Storage, 24 | ) -> Result<()> { 25 | let engine = Engine::new(); 26 | let mut scope = Scope::new(); 27 | let dyn_config = to_dynamic(&config).map_err(|e| anyhow!("to_dynamic err: {:?}", e))?; 28 | scope.push("config", dyn_config); 29 | 30 | engine 31 | .eval_with_scope(&mut scope, content) 32 | .map_err(|e| anyhow!("Failed to evaluate rhai: {:?}", e))?; 33 | 34 | if let Some(cfg) = scope.get_value("config") { 35 | *config = from_dynamic(&cfg).map_err(|e| anyhow!("from_dynamic err: {:?}", e))?; 36 | } else { 37 | return Err(anyhow!("Failed to get config from rhai")); 38 | } 39 | 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl Builder for Rhai { 45 | const NAME: &'static str = "rhai"; 46 | 47 | type Config = Rhai; 48 | type Item = Rhai; 49 | 50 | fn build(_cfg: Self::Config) -> rd_interface::Result { 51 | Ok(Rhai {}) 52 | } 53 | } 54 | 55 | impl IntoDyn for Rhai { 56 | fn into_dyn(self) -> BoxImporter { 57 | Box::new(self) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/config/select_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use anyhow::Result; 4 | use rabbit_digger::Config; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::storage::Storage; 8 | 9 | #[derive(Debug, Serialize, Deserialize)] 10 | pub struct SelectMap(HashMap); 11 | 12 | impl SelectMap { 13 | pub async fn from_cache(id: &str, cache: &dyn Storage) -> Result { 14 | let select_map = cache 15 | .get(id) 16 | .await? 17 | .map(|i| serde_json::from_str(&i.content).unwrap_or_default()) 18 | .unwrap_or_default(); 19 | Ok(SelectMap(select_map)) 20 | } 21 | pub async fn write_cache(&self, id: &str, cache: &dyn Storage) -> Result<()> { 22 | cache.set(id, &serde_json::to_string(&self.0)?).await 23 | } 24 | pub async fn apply_config(&self, config: &mut Config) { 25 | for (net, selected) in &self.0 { 26 | if let Some(n) = config.net.get_mut(net) { 27 | if n.net_type == "select" { 28 | if let Some(o) = n.opt.as_object_mut() { 29 | if o.get("list") 30 | .into_iter() 31 | .filter_map(|v| v.as_array()) 32 | .flatten() 33 | .flat_map(|v| v.as_str()) 34 | .any(|i| i == selected) 35 | { 36 | o.insert("selected".to_string(), selected.to_string().into()); 37 | } else { 38 | tracing::info!("The selected({}/{}) in the select map is not in the list, skip overriding.", selected, net); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | pub fn insert(&mut self, key: String, value: String) -> Option { 46 | self.0.insert(key, value) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use config::ConfigManager; 3 | pub use rabbit_digger; 4 | use rabbit_digger::{RabbitDigger, Registry}; 5 | use yaml_merge_keys::merge_keys_serde; 6 | 7 | #[cfg(feature = "api_server")] 8 | pub mod api_server; 9 | pub mod config; 10 | pub mod log; 11 | pub mod schema; 12 | mod select; 13 | pub mod storage; 14 | pub mod util; 15 | 16 | pub fn get_registry() -> Result { 17 | let mut registry = Registry::new_with_builtin()?; 18 | 19 | #[cfg(feature = "ss")] 20 | registry.init_with_registry("ss", ss::init)?; 21 | #[cfg(feature = "trojan")] 22 | registry.init_with_registry("trojan", trojan::init)?; 23 | #[cfg(feature = "rpc")] 24 | registry.init_with_registry("rpc", rpc::init)?; 25 | #[cfg(feature = "raw")] 26 | registry.init_with_registry("raw", raw::init)?; 27 | #[cfg(feature = "obfs")] 28 | registry.init_with_registry("obfs", obfs::init)?; 29 | 30 | registry.init_with_registry("rabbit-digger-pro", select::init)?; 31 | 32 | Ok(registry) 33 | } 34 | 35 | pub fn deserialize_config(s: &str) -> Result { 36 | let raw_yaml = serde_yaml::from_str(s)?; 37 | let merged = merge_keys_serde(raw_yaml)?; 38 | Ok(serde_yaml::from_value(merged)?) 39 | } 40 | 41 | pub struct App { 42 | pub rd: RabbitDigger, 43 | pub cfg_mgr: ConfigManager, 44 | } 45 | 46 | #[derive(Default, Debug)] 47 | pub struct ApiServer { 48 | pub bind: Option, 49 | pub access_token: Option, 50 | pub web_ui: Option, 51 | } 52 | 53 | impl App { 54 | pub async fn new() -> Result { 55 | let rd = RabbitDigger::new(get_registry()?).await?; 56 | let cfg_mgr = ConfigManager::new().await?; 57 | 58 | Ok(Self { rd, cfg_mgr }) 59 | } 60 | pub async fn run_api_server(&self, api_server: ApiServer) -> Result<()> { 61 | #[cfg(feature = "api_server")] 62 | if let Some(bind) = api_server.bind { 63 | api_server::ApiServer { 64 | rabbit_digger: self.rd.clone(), 65 | config_manager: self.cfg_mgr.clone(), 66 | access_token: api_server.access_token, 67 | web_ui: api_server.web_ui, 68 | } 69 | .run(&bind) 70 | .await 71 | .context("Failed to run api server.")?; 72 | } 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::OnceCell; 2 | use std::io::Write; 3 | use tokio::sync::broadcast; 4 | 5 | static BROADCAST: OnceCell>> = OnceCell::new(); 6 | 7 | pub fn get_sender() -> &'static broadcast::Sender> { 8 | BROADCAST.get_or_init(|| { 9 | let (tx, _) = broadcast::channel::>(32); 10 | tx 11 | }) 12 | } 13 | 14 | pub struct LogWriter { 15 | sender: broadcast::Sender>, 16 | } 17 | 18 | impl Write for LogWriter { 19 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 20 | self.sender.send(buf.into()).ok(); 21 | Ok(buf.len()) 22 | } 23 | 24 | fn flush(&mut self) -> std::io::Result<()> { 25 | Ok(()) 26 | } 27 | } 28 | 29 | impl LogWriter { 30 | pub fn new() -> Self { 31 | LogWriter { 32 | sender: get_sender().clone(), 33 | } 34 | } 35 | } 36 | 37 | impl Default for LogWriter { 38 | fn default() -> Self { 39 | Self::new() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/select.rs: -------------------------------------------------------------------------------- 1 | use rd_interface::{ 2 | async_trait, 3 | prelude::*, 4 | registry::{Builder, NetRef}, 5 | Error, INet, Net, Registry, Result, 6 | }; 7 | 8 | #[rd_config] 9 | #[derive(Debug, Clone)] 10 | pub struct SelectNetConfig { 11 | selected: NetRef, 12 | list: Vec, 13 | } 14 | 15 | pub struct SelectNet { 16 | selected: Net, 17 | } 18 | 19 | impl SelectNet { 20 | pub fn new(config: SelectNetConfig) -> Result { 21 | if config.list.is_empty() { 22 | return Err(Error::Other("select list is empty".into())); 23 | } 24 | 25 | Ok(SelectNet { 26 | selected: config.selected.value_cloned(), 27 | }) 28 | } 29 | fn net(&self) -> Option<&Net> { 30 | Some(&self.selected) 31 | } 32 | } 33 | 34 | #[async_trait] 35 | impl INet for SelectNet { 36 | fn provide_tcp_connect(&self) -> Option<&dyn rd_interface::TcpConnect> { 37 | self.net()?.provide_tcp_connect() 38 | } 39 | 40 | fn provide_tcp_bind(&self) -> Option<&dyn rd_interface::TcpBind> { 41 | self.net()?.provide_tcp_bind() 42 | } 43 | 44 | fn provide_udp_bind(&self) -> Option<&dyn rd_interface::UdpBind> { 45 | self.net()?.provide_udp_bind() 46 | } 47 | 48 | fn provide_lookup_host(&self) -> Option<&dyn rd_interface::LookupHost> { 49 | self.net()?.provide_lookup_host() 50 | } 51 | } 52 | 53 | impl Builder for SelectNet { 54 | const NAME: &'static str = "select"; 55 | type Config = SelectNetConfig; 56 | type Item = Self; 57 | 58 | fn build(config: Self::Config) -> Result { 59 | SelectNet::new(config) 60 | } 61 | } 62 | 63 | pub fn init(registry: &mut Registry) -> Result<()> { 64 | registry.add_net::(); 65 | Ok(()) 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use rd_interface::IntoDyn; 71 | use rd_std::tests::{assert_net_provider, ProviderCapability, TestNet}; 72 | 73 | use super::*; 74 | 75 | #[test] 76 | fn test_provider() { 77 | let net = NetRef::new_with_value("test".into(), TestNet::new().into_dyn()); 78 | 79 | let select = SelectNet::new(SelectNetConfig { 80 | selected: net.clone(), 81 | list: vec![net], 82 | }) 83 | .unwrap() 84 | .into_dyn(); 85 | 86 | assert_net_provider( 87 | &select, 88 | ProviderCapability { 89 | tcp_connect: true, 90 | tcp_bind: true, 91 | udp_bind: true, 92 | lookup_host: true, 93 | }, 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use anyhow::Result; 4 | use rd_interface::async_trait; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | pub use self::{ 8 | file::{FileStorage, FolderType}, 9 | memory::MemoryCache, 10 | }; 11 | 12 | mod file; 13 | mod memory; 14 | 15 | #[derive(Debug, Clone, Deserialize, Serialize)] 16 | pub struct StorageItem { 17 | pub updated_at: SystemTime, 18 | pub content: String, 19 | } 20 | 21 | #[derive(Debug, Clone, Deserialize, Serialize)] 22 | pub struct StorageKey { 23 | pub updated_at: SystemTime, 24 | pub key: String, 25 | } 26 | 27 | #[async_trait] 28 | pub trait Storage: Send + Sync { 29 | async fn get_updated_at(&self, key: &str) -> Result>; 30 | async fn get(&self, key: &str) -> Result>; 31 | async fn set(&self, key: &str, value: &str) -> Result<()>; 32 | async fn remove(&self, key: &str) -> Result<()>; 33 | async fn keys(&self) -> Result>; 34 | async fn clear(&self) -> Result<()>; 35 | } 36 | -------------------------------------------------------------------------------- /src/storage/memory.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, time::SystemTime}; 2 | 3 | use super::{Storage, StorageItem, StorageKey}; 4 | use anyhow::Result; 5 | use parking_lot::RwLock; 6 | use rd_interface::async_trait; 7 | 8 | pub struct MemoryCache { 9 | cache: RwLock>, 10 | } 11 | 12 | impl MemoryCache { 13 | #[allow(dead_code)] 14 | pub async fn new() -> Result { 15 | Ok(MemoryCache { 16 | cache: RwLock::new(HashMap::new()), 17 | }) 18 | } 19 | } 20 | 21 | #[async_trait] 22 | impl Storage for MemoryCache { 23 | async fn get_updated_at(&self, key: &str) -> Result> { 24 | Ok(self.cache.read().get(key).map(|item| item.updated_at)) 25 | } 26 | 27 | async fn get(&self, key: &str) -> Result> { 28 | Ok(self.cache.read().get(key).cloned()) 29 | } 30 | 31 | async fn set(&self, key: &str, value: &str) -> Result<()> { 32 | self.cache.write().insert( 33 | key.to_string(), 34 | StorageItem { 35 | updated_at: SystemTime::now(), 36 | content: value.to_string(), 37 | }, 38 | ); 39 | Ok(()) 40 | } 41 | 42 | async fn keys(&self) -> Result> { 43 | Ok(self 44 | .cache 45 | .read() 46 | .iter() 47 | .map(|(key, i)| StorageKey { 48 | key: key.to_string(), 49 | updated_at: i.updated_at, 50 | }) 51 | .collect()) 52 | } 53 | 54 | async fn remove(&self, key: &str) -> Result<()> { 55 | self.cache.write().remove(key); 56 | Ok(()) 57 | } 58 | 59 | async fn clear(&self) -> Result<()> { 60 | self.cache.write().clear(); 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub use debounce_stream::{DebounceStream, DebounceStreamExt}; 2 | pub use exit_stream::exit_stream; 3 | 4 | mod debounce_stream; 5 | mod exit_stream; 6 | -------------------------------------------------------------------------------- /src/util/debounce_stream.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | task::{Context, Poll}, 4 | time::Duration, 5 | }; 6 | 7 | use futures::{Future, Stream, StreamExt}; 8 | use pin_project_lite::pin_project; 9 | use tokio::time::{sleep, Sleep}; 10 | 11 | pin_project! { 12 | #[derive(Debug)] 13 | pub struct DebounceStream { 14 | #[pin] 15 | inner: S, 16 | timer: Option>>, 17 | item: Option, 18 | delay: Duration, 19 | } 20 | } 21 | 22 | pub trait DebounceStreamExt: Stream { 23 | fn debounce(self, duration: Duration) -> DebounceStream 24 | where 25 | Self: Sized, 26 | { 27 | DebounceStream { 28 | inner: self, 29 | timer: None, 30 | item: None, 31 | delay: duration, 32 | } 33 | } 34 | } 35 | impl DebounceStreamExt for T {} 36 | 37 | impl Stream for DebounceStream 38 | where 39 | S: Stream + Unpin, 40 | { 41 | type Item = S::Item; 42 | 43 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 44 | let mut this = self.project(); 45 | match this.inner.poll_next_unpin(cx) { 46 | Poll::Ready(r) => { 47 | *this.timer = Some(Box::pin(sleep(*this.delay))); 48 | *this.item = r; 49 | } 50 | Poll::Pending => {} 51 | }; 52 | let poll_timer = this.timer.as_mut().map(|t| Future::poll(Pin::new(t), cx)); 53 | if let Some(Poll::Ready(_)) = poll_timer { 54 | *this.timer = None; 55 | Poll::Ready(this.item.take()) 56 | } else { 57 | Poll::Pending 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/util/exit_stream.rs: -------------------------------------------------------------------------------- 1 | use async_stream::try_stream; 2 | use futures::Stream; 3 | use std::io; 4 | use tokio::signal::ctrl_c; 5 | 6 | pub fn exit_stream() -> impl Stream> { 7 | let mut times = 0; 8 | try_stream! { 9 | loop { 10 | ctrl_c().await?; 11 | times += 1; 12 | yield times; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/relay_clash.yml: -------------------------------------------------------------------------------- 1 | rules: [] 2 | proxies: 3 | - name: proxy1 4 | type: "ss" 5 | server: "proxy1" 6 | port: 1 7 | cipher: chacha20-ietf 8 | password: p 9 | - name: proxy2 10 | type: "ss" 11 | server: "proxy2" 12 | port: 2 13 | cipher: chacha20-ietf 14 | password: p 15 | proxy-groups: 16 | - name: relay 17 | type: relay 18 | proxies: 19 | - proxy1 20 | - proxy2 21 | -------------------------------------------------------------------------------- /tests/relay_rdp.yml: -------------------------------------------------------------------------------- 1 | id: '' 2 | net: 3 | proxy1: 4 | type: shadowsocks 5 | server: proxy1:1 6 | cipher: chacha20-ietf 7 | password: p 8 | udp: false 9 | proxy2: 10 | type: shadowsocks 11 | server: proxy2:2 12 | cipher: chacha20-ietf 13 | password: p 14 | udp: false 15 | relay: 16 | type: shadowsocks 17 | server: proxy2:2 18 | cipher: chacha20-ietf 19 | password: p 20 | udp: false 21 | net: 22 | type: shadowsocks 23 | server: proxy1:1 24 | cipher: chacha20-ietf 25 | password: p 26 | udp: false 27 | net: 28 | type: alias 29 | net: local 30 | server: {} 31 | --------------------------------------------------------------------------------