├── .cargo └── config.toml ├── .github └── workflows │ ├── clippy.yml │ ├── release-plz.yml │ ├── release.yml │ ├── rust-fmt.yml │ ├── rust.yml │ └── typos.yml ├── .gitignore ├── Cargo.toml ├── Dockerfile ├── README.md ├── _typos.toml ├── bin ├── agent │ ├── .gitignore │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── benchmark_quic.sh │ ├── benchmark_tcp.sh │ ├── benchmark_tls.sh │ ├── examples │ │ └── benchmark_clients.rs │ ├── node_local_quic.sh │ ├── node_local_tcp.sh │ ├── node_local_tls.sh │ └── src │ │ ├── connection.rs │ │ ├── connection │ │ ├── quic.rs │ │ ├── quic │ │ │ ├── helper.rs │ │ │ └── no_servername_verify.rs │ │ ├── tcp.rs │ │ └── tls.rs │ │ ├── lib.rs │ │ ├── local_tunnel.rs │ │ ├── local_tunnel │ │ ├── registry.rs │ │ └── tcp.rs │ │ └── main.rs └── relayer │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── debug.plist │ ├── run_local_node1.sh │ ├── run_local_node2.sh │ ├── run_local_node3.sh │ └── src │ ├── agent.rs │ ├── agent │ ├── quic.rs │ ├── tcp.rs │ └── tls.rs │ ├── lib.rs │ ├── main.rs │ ├── metrics.rs │ ├── proxy.rs │ ├── proxy │ ├── http.rs │ ├── rtsp.rs │ └── tls.rs │ └── quic.rs ├── crates ├── cert_utils │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── protocol │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── certs │ │ ├── cluster.cert │ │ ├── cluster.key │ │ ├── tunnel.cert │ │ └── tunnel.key │ └── src │ │ ├── cluster.rs │ │ ├── key.rs │ │ ├── lib.rs │ │ ├── proxy.rs │ │ ├── services.rs │ │ ├── stream.rs │ │ └── time.rs └── protocol_ed25519 │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── src │ └── lib.rs ├── deny.toml ├── renovate.json └── rustfmt.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [net] 2 | git-fetch-with-cli = true 3 | 4 | # We dont build for mipsel then we dont need this 5 | # [patch.crates-io] 6 | # rustls = { git = "https://github.com/giangndm/rustls.git", branch = "mipsel-patch" } 7 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | # rust-clippy is a tool that runs a bunch of lints to catch common 2 | # mistakes in your Rust code and help improve your Rust code. 3 | # More details at https://github.com/rust-lang/rust-clippy 4 | # and https://rust-lang.github.io/rust-clippy/ 5 | 6 | name: rust-clippy analyze 7 | 8 | on: 9 | push: 10 | branches: ["main"] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: ["main"] 14 | schedule: 15 | - cron: "29 19 * * 2" 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 19 | cancel-in-progress: true 20 | 21 | env: 22 | CARGO_TERM_COLOR: always 23 | 24 | jobs: 25 | rust-clippy-analyze: 26 | name: Run rust-clippy analyzing 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: read 30 | security-events: write 31 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v4 35 | 36 | - uses: actions/cache@v3 37 | id: cache-cargo 38 | with: 39 | path: | 40 | ~/.cargo/registry 41 | ~/.cargo/git 42 | target 43 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 44 | restore-keys: ${{ runner.os }}-cargo- 45 | 46 | - name: Install Protoc 47 | uses: arduino/setup-protoc@v3 48 | with: 49 | version: "25.1" 50 | repo-token: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: Run rust-clippy 53 | run: cargo clippy --all-targets --all-features -- -D warnings 54 | 55 | - name: Install required cargo 56 | run: cargo install clippy-sarif sarif-fmt 57 | 58 | - name: Run rust-sarif 59 | run: cargo clippy --all-features --message-format=json | 60 | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 61 | 62 | - name: Upload analysis results to GitHub 63 | uses: github/codeql-action/upload-sarif@v3 64 | with: 65 | sarif_file: rust-clippy-results.sarif 66 | wait-for-processing: true 67 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [main] 5 | tags: 6 | - "v*.*.*" 7 | create: 8 | tags: 9 | - "v*.*.*" 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | env: 17 | AGENT_NAME: atm0s-reverse-proxy-agent 18 | RELAYER_NAME: atm0s-reverse-proxy-relayer 19 | ARTIFACT_DIR: release-builds 20 | REGISTRY: ghcr.io 21 | IMAGE_NAME: ${{ github.repository }} 22 | 23 | jobs: 24 | build-release: 25 | name: build-release 26 | runs-on: ${{ matrix.os }} 27 | env: 28 | RUST_BACKTRACE: 1 29 | strategy: 30 | matrix: 31 | build: 32 | - linux gnu x64 33 | - linux musl x64 34 | - linux gnu aarch64 35 | - linux musl aarch64 36 | # - linux gnueabihf arm 37 | # - linux gnueabihf armv7 38 | # - linux gnu mips 39 | # - linux gnuabi64 mips64 40 | # - linux gnuabi64 mips64el 41 | # - linux gnu mipsel 42 | - macos x64 43 | - macos aarch64 44 | include: 45 | - build: linux gnu x64 46 | os: ubuntu-latest 47 | rust: stable 48 | target: x86_64-unknown-linux-gnu 49 | cross: false 50 | - build: linux musl x64 51 | os: ubuntu-latest 52 | rust: stable 53 | target: x86_64-unknown-linux-musl 54 | cross: false 55 | - build: linux gnu aarch64 56 | os: ubuntu-latest 57 | rust: stable 58 | target: aarch64-unknown-linux-gnu 59 | cross: true 60 | - build: linux musl aarch64 61 | os: ubuntu-latest 62 | rust: stable 63 | target: aarch64-unknown-linux-musl 64 | cross: true 65 | # - build: linux gnueabihf arm 66 | # os: ubuntu-latest 67 | # rust: stable 68 | # target: arm-unknown-linux-gnueabihf 69 | # cross: true 70 | # - build: linux gnueabihf armv7 71 | # os: ubuntu-latest 72 | # rust: stable 73 | # target: armv7-unknown-linux-gnueabihf 74 | # cross: true 75 | # - build: linux gnu mips 76 | # os: ubuntu-latest 77 | # rust: 1.71.1 78 | # target: mips-unknown-linux-gnu 79 | # cross: true 80 | # - build: linux gnuabi64 mips64 81 | # os: ubuntu-latest 82 | # rust: 1.71.1 83 | # target: mips64-unknown-linux-gnuabi64 84 | # cross: true 85 | # - build: linux gnuabi64 mips64el 86 | # os: ubuntu-latest 87 | # rust: 1.71.1 88 | # target: mips64el-unknown-linux-gnuabi64 89 | # cross: true 90 | # - build: linux gnu mipsel 91 | # os: ubuntu-latest 92 | # rust: 1.71.1 93 | # target: mipsel-unknown-linux-gnu 94 | # cross: true 95 | - build: linux musl aarch64 96 | os: ubuntu-latest 97 | rust: stable 98 | target: aarch64-unknown-linux-musl 99 | cross: true 100 | - build: macos x64 101 | os: macos-latest 102 | rust: stable 103 | target: x86_64-apple-darwin 104 | cross: false 105 | - build: macos aarch64 106 | os: macos-latest 107 | rust: stable 108 | target: aarch64-apple-darwin 109 | cross: true 110 | steps: 111 | - name: Checkout repository 112 | uses: actions/checkout@v4 113 | 114 | - name: Install Rust 115 | uses: actions-rs/toolchain@v1 116 | with: 117 | toolchain: ${{ matrix.rust }} 118 | profile: minimal 119 | override: true 120 | target: ${{ matrix.target }} 121 | 122 | - name: Install dev-tools 123 | if: matrix.os == 'ubuntu-latest' 124 | run: sudo apt-get install -y --no-install-recommends pkg-config musl-dev musl-tools 125 | 126 | - name: Build agent 127 | uses: actions-rs/cargo@v1 128 | with: 129 | use-cross: ${{ matrix.cross }} 130 | command: build 131 | args: --verbose --release --package ${{ env.AGENT_NAME }} --target ${{ matrix.target }} 132 | 133 | - name: Build relayer 134 | uses: actions-rs/cargo@v1 135 | with: 136 | use-cross: ${{ matrix.cross }} 137 | command: build 138 | args: --verbose --release --package ${{ env.RELAYER_NAME }} --target ${{ matrix.target }} 139 | 140 | - name: Rename file 141 | run: | 142 | mv ./target/${{ matrix.target }}/release/${{ env.AGENT_NAME }} ${{ env.AGENT_NAME }}-${{ matrix.target }} 143 | mv ./target/${{ matrix.target }}/release/${{ env.RELAYER_NAME }} ${{ env.RELAYER_NAME }}-${{ matrix.target }} 144 | 145 | - name: Upload Artifact to Summary 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: ${{ matrix.target }} 149 | path: | 150 | ${{ env.AGENT_NAME }}-${{ matrix.target }} 151 | ${{ env.RELAYER_NAME }}-${{ matrix.target }} 152 | 153 | - name: Upload agent binaries to release 154 | if: startsWith(github.ref, 'refs/tags/') 155 | uses: svenstaro/upload-release-action@v2 156 | with: 157 | repo_token: ${{ secrets.GITHUB_TOKEN }} 158 | file: ${{ env.AGENT_NAME }}-${{ matrix.target }} 159 | asset_name: ${{ env.AGENT_NAME }}-${{ matrix.target }} 160 | tag: ${{ github.ref }} 161 | overwrite: true 162 | 163 | - name: Upload relayer binaries to release 164 | if: startsWith(github.ref, 'refs/tags/') 165 | uses: svenstaro/upload-release-action@v2 166 | with: 167 | repo_token: ${{ secrets.GITHUB_TOKEN }} 168 | file: ${{ env.RELAYER_NAME }}-${{ matrix.target }} 169 | asset_name: ${{ env.RELAYER_NAME }}-${{ matrix.target }} 170 | tag: ${{ github.ref }} 171 | overwrite: true 172 | 173 | create-release: 174 | # only run if not a tags build 175 | if: startsWith(github.ref, 'refs/tags/') == false 176 | needs: build-release 177 | runs-on: ubuntu-latest 178 | steps: 179 | - uses: actions/download-artifact@v3 180 | - name: Display structure of downloaded files 181 | run: ls -R 182 | - name: create_release 183 | id: create_release 184 | uses: marvinpinto/action-automatic-releases@latest 185 | with: 186 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 187 | automatic_release_tag: ${{ github.event_name == 'workflow_dispatch' && 'latest' || (github.ref == 'refs/heads/main' && 'latest') || github.ref }} 188 | title: Build ${{ github.event_name == 'workflow_dispatch' && 'development' || github.ref }} 189 | files: | 190 | */* 191 | prerelease: true 192 | 193 | deploy-docker: 194 | needs: build-release 195 | runs-on: ubuntu-latest 196 | steps: 197 | - name: Set up QEMU 198 | uses: docker/setup-qemu-action@v3 199 | - name: Set up Docker Buildx 200 | uses: docker/setup-buildx-action@v3 201 | - name: Checkout repository 202 | uses: actions/checkout@v4 203 | - uses: actions/download-artifact@v3 204 | # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. 205 | - name: Log in to the Container registry 206 | uses: docker/login-action@v3 207 | with: 208 | registry: ${{ env.REGISTRY }} 209 | username: ${{ github.actor }} 210 | password: ${{ secrets.GITHUB_TOKEN }} 211 | # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. 212 | - name: Extract metadata (tags, labels) for Docker 213 | id: meta 214 | uses: docker/metadata-action@v5 215 | with: 216 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 217 | # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. 218 | # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. 219 | # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. 220 | - name: Build and push Docker image 221 | uses: docker/build-push-action@v5 222 | with: 223 | context: . 224 | push: true 225 | platforms: linux/amd64,linux/arm64 226 | tags: ${{ steps.meta.outputs.tags }} 227 | labels: ${{ steps.meta.outputs.labels }} 228 | -------------------------------------------------------------------------------- /.github/workflows/rust-fmt.yml: -------------------------------------------------------------------------------- 1 | name: rust-fmt analyze 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: ["main"] 9 | schedule: 10 | - cron: "29 19 * * 2" 11 | 12 | concurrency: 13 | # One build per PR, branch or tag 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 15 | cancel-in-progress: true 16 | 17 | env: 18 | CARGO_TERM_COLOR: always 19 | 20 | jobs: 21 | rust-fmt-analyze: 22 | name: Run rust-fmt analyzing 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v4 27 | 28 | - uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.cargo/bin/ 32 | ~/.cargo/registry/index/ 33 | ~/.cargo/registry/cache/ 34 | ~/.cargo/git/db/ 35 | target/ 36 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 37 | 38 | - name: cargo fmt 39 | run: cargo fmt --all -- --check 40 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | paths-ignore: 7 | - "docs/**" 8 | 9 | pull_request: 10 | branches: ["main"] 11 | paths-ignore: 12 | - "docs/**" 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | jobs: 18 | code-coverage: 19 | runs-on: ubuntu-latest 20 | env: 21 | CARGO_TERM_COLOR: always 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install deps 25 | run: | 26 | sudo apt-get update 27 | sudo apt install -y libsoxr-dev libopus-dev libssl-dev libfdk-aac-dev 28 | 29 | - uses: actions/cache@v3 30 | with: 31 | path: | 32 | ~/.cargo/bin/ 33 | ~/.cargo/registry/index/ 34 | ~/.cargo/registry/cache/ 35 | ~/.cargo/git/db/ 36 | target/ 37 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 38 | 39 | - name: Install Protoc 40 | uses: arduino/setup-protoc@v3 41 | with: 42 | version: "25.1" 43 | repo-token: ${{ secrets.GITHUB_TOKEN }} 44 | - name: Install cargo-llvm-cov 45 | uses: taiki-e/install-action@cargo-llvm-cov 46 | - name: Running cargo test 47 | run: cargo test --all-features --workspace 48 | - name: Generate code coverage 49 | run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info 50 | - name: Upload coverage to Codecov 51 | uses: codecov/codecov-action@v4 52 | with: 53 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 54 | files: lcov.info 55 | fail_ci_if_error: false 56 | cargo-deny: 57 | name: cargo-deny 58 | 59 | # TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved 60 | strategy: 61 | fail-fast: false 62 | matrix: 63 | platform: 64 | - x86_64-unknown-linux-gnu 65 | # - x86_64-unknown-linux-musl 66 | - aarch64-unknown-linux-gnu 67 | # - arm-unknown-linux-gnueabihf 68 | # - armv7-unknown-linux-gnueabihf 69 | # - mips-unknown-linux-gnu 70 | # - mips64-unknown-linux-gnuabi64 71 | # - mips64el-unknown-linux-gnuabi64 72 | # - mipsel-unknown-linux-gnu 73 | # - aarch64-unknown-linux-musl 74 | - x86_64-apple-darwin 75 | - aarch64-apple-darwin 76 | # - x86_64-pc-windows-gnu 77 | # - x86_64-pc-windows-msvc 78 | 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: EmbarkStudios/cargo-deny-action@v1 83 | with: 84 | command: check 85 | log-level: error 86 | arguments: --all-features --target ${{ matrix.platform }} 87 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | name: Typos 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | typos: 14 | runs-on: ubuntu-latest 15 | env: 16 | CARGO_TERM_COLOR: always 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Check spelling issues 20 | uses: crate-ci/typos@master 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "bin/agent", 6 | "bin/relayer", 7 | "crates/*", 8 | ] 9 | 10 | [workspace.dependencies] 11 | p2p = { package = "atm0s-small-p2p", version = "0.2" } 12 | protocol = { path = "crates/protocol", package = "atm0s-reverse-proxy-protocol", version = "0.3.1" } 13 | protocol-ed25519 = { path = "crates/protocol_ed25519", package = "atm0s-reverse-proxy-protocol-ed25519", version = "0.1.5" } 14 | 15 | log = "0.4" 16 | tokio-yamux = "0.3" 17 | clap = "4.4" 18 | argh = "=0.1.13" # small cli 19 | async-trait = "0.1" 20 | tokio = "1" 21 | httparse = "1.8" 22 | tls-parser = "0.12" 23 | rtsp-types = "0.1" 24 | tracing-subscriber = "0.3" 25 | picolog = "1.0" 26 | atm0s-sdn = "0.2" 27 | serde = "1.0" 28 | bincode = "1.3" 29 | metrics-dashboard = "0.3" 30 | poem = "2.0" 31 | metrics = "0.22" 32 | quinn = "0.11" 33 | rustls = "0.23" 34 | ed25519-dalek = "2.1" 35 | rand = "0.8" 36 | rcgen = "0.13" 37 | url = "2.5" 38 | base64 = "0.22" 39 | local-ip-address = "0.6" 40 | derive_more = "1.0" 41 | thiserror = "2.0" 42 | anyhow = "1.0" 43 | parking_lot = "0.12" 44 | futures = "0.3" 45 | tokio-util = "0.7" 46 | test-log = "0.2" 47 | lru = "0.12" 48 | tokio-native-tls = "0.3.1" 49 | tokio-rustls = "0.26.1" 50 | 51 | [profile.release] 52 | strip = true # Automatically strip symbols from the binary. 53 | opt-level = "z" # Optimize for size. 54 | lto = true 55 | codegen-units = 1 56 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 as base 2 | ARG TARGETPLATFORM 3 | COPY . /tmp 4 | WORKDIR /tmp 5 | 6 | RUN echo $TARGETPLATFORM 7 | RUN ls -R /tmp/ 8 | # move the binary to root based on platform 9 | RUN case $TARGETPLATFORM in \ 10 | "linux/amd64") BUILD=x86_64-unknown-linux-gnu ;; \ 11 | "linux/arm64") BUILD=aarch64-unknown-linux-gnu ;; \ 12 | *) exit 1 ;; \ 13 | esac; \ 14 | mv /tmp/$BUILD/atm0s-reverse-proxy-agent-$BUILD /agent; \ 15 | mv /tmp/$BUILD/atm0s-reverse-proxy-relayer-$BUILD /relayer; \ 16 | chmod +x /agent; \ 17 | chmod +x /relayer; 18 | 19 | FROM ubuntu:22.04 20 | 21 | COPY --from=base /relayer /relayer 22 | COPY --from=base /agent /agent 23 | 24 | ENTRYPOINT ["/relayer"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decentralized reverse proxy for HomeAssistant, IoT and more 2 | 3 | This project aims to create an innovative decentralized reverse proxy server for HomeAssistant, IoT and more. The server allows contributions from anyone and provides a fast and optimized path between clients. 4 | 5 | If you find it interesting or believe it could be helpful, we welcome your contributions to the codebase or consider starring the repository to show your support and motivate our team! 6 | 7 | ## Features 8 | 9 | - **Decentralized:** The server is designed to be decentralized, allowing anyone to contribute and participate in the network. More details: [atm0s-sdn](https://github.com/8xff/atm0s-sdn) 10 | 11 | - **Fast Relay:** The relay server ensures fast and efficient communication between clients, optimizing the path for data transmission using the atm0s-sdn overlay network. 12 | 13 | - **Data Safety:** The TCP proxy used in this project is based on SNI (Server Name Indication), ensuring the safety and integrity of the transmitted data. 14 | 15 | - **Written in Rust:** The project is implemented in Rust, a modern and efficient programming language known for its performance and memory safety. 16 | 17 | - **No account required:** Each client will be identified by a public-private key, just connect and it works. 18 | 19 | ## Check list 20 | 21 | - [x] Decentralized Routing, Relay between nodes 22 | - [x] TLS(SNI) tunneling 23 | - [x] HTTP tunneling 24 | - [x] Stats Dashboard 25 | - [x] Anonymous client 26 | - [ ] Anonymous server contribution (WIP) 27 | 28 | ## Performance 29 | 30 | Bellow is benchmarking results with Mac M1, and all nodes is running locally, it it very early version so it can be improve after finish all features: 31 | 32 | - Direct http 33 | 34 | ```bash 35 | wrk http://localhost:3000 36 | Running 10s test @ http://localhost:3000 37 | 2 threads and 10 connections 38 | Thread Stats Avg Stdev Max +/- Stdev 39 | Latency 113.58us 32.97us 3.68ms 77.81% 40 | Req/Sec 41.60k 1.27k 43.00k 89.60% 41 | 836084 requests in 10.10s, 143.52MB read 42 | Requests/sec: 82780.91 43 | Transfer/sec: 14.21MB 44 | ``` 45 | 46 | - Single node 47 | 48 | ```bash 49 | wrk http://localhost:3000 50 | Running 10s test @ http://localhost:3000 51 | 2 threads and 10 connections 52 | Thread Stats Avg Stdev Max +/- Stdev 53 | Latency 407.38us 489.34us 16.67ms 98.87% 54 | Req/Sec 13.18k 1.67k 17.61k 77.72% 55 | 264801 requests in 10.10s, 45.46MB read 56 | Requests/sec: 26218.89 57 | Transfer/sec: 4.50MB 58 | ``` 59 | 60 | - Two nodes 61 | 62 | ```bash 63 | wrk http://localhost:3000 64 | Running 10s test @ http://localhost:3000 65 | 2 threads and 10 connections 66 | Thread Stats Avg Stdev Max +/- Stdev 67 | Latency 678.58us 681.76us 19.83ms 98.50% 68 | Req/Sec 8.06k 1.19k 9.60k 64.85% 69 | 162101 requests in 10.10s, 27.83MB read 70 | Requests/sec: 16049.83 71 | Transfer/sec: 2.76MB 72 | ``` 73 | 74 | ## Getting Started 75 | 76 | To get started with the Decentralized HomeAssistant Proxy, follow these steps: 77 | 78 | 1. Clone the repository: 79 | 80 | ```shell 81 | git clone https://github.com/8xFF/atm0s-reverse-proxy.git 82 | ``` 83 | 84 | 2. Build the project: 85 | 86 | ```shell 87 | cd atm0s-reverse-proxy 88 | cargo build --release 89 | ``` 90 | 91 | 3. Run the server node1: 92 | 93 | ```shell 94 | ./target/release/relayer \ 95 | --api-port 10001 \ 96 | --http-port 11001 \ 97 | --https-port 12001 \ 98 | --connector-port 0.0.0.0:13001 \ 99 | --root-domain local.ha.8xff.io \ 100 | --sdn-node-id 1 101 | ``` 102 | 103 | 3. Run the server node2: 104 | 105 | ```shell 106 | ./target/release/relayer \ 107 | --api-port 10002 \ 108 | --http-port 11002 \ 109 | --https-port 12002 \ 110 | --connector-port 0.0.0.0:13002 \ 111 | --root-domain local.ha.8xff.io \ 112 | --sdn-node-id 2 \ 113 | --sdn-seeds '1@/ip4/192.168.1.39/udp/50001' 114 | ``` 115 | 116 | 4. Run the client: 117 | 118 | ```shell 119 | ./target/release/agent \ 120 | --connector-protocol tcp \ 121 | --connector-addr 127.0.0.1:13001 \ 122 | --http-dest 127.0.0.1:8080 \ 123 | --https-dest 127.0.0.1:8443 124 | ``` 125 | Client will print out assigned domain like: `a5fae3d220cb062c5aed9bd57d82f226.local.ha.8xff.io`, now we can access to local 8080 service with address: 126 | 127 | - Proxy over single node1: 128 | http://a5fae3d220cb062c5aed9bd57d82f226.local.ha.8xff.io:11001 129 | 130 | - Proxy with relay over node2 -> node1: 131 | http://a5fae3d220cb062c5aed9bd57d82f226.local.ha.8xff.io:11002 132 | 133 | Note that above url only can access in same machine. 134 | 135 | ## Contributing 136 | 137 | Contributions are welcome! If you'd like to contribute to the project, please follow the guidelines outlined in [CONTRIBUTING.md](CONTRIBUTING.md). 138 | 139 | ## License 140 | 141 | This project is licensed under the [MIT License](LICENSE). 142 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | # Don't correct the app name "wrk" 3 | wrk = "wrk" 4 | -------------------------------------------------------------------------------- /bin/agent/.gitignore: -------------------------------------------------------------------------------- 1 | local_key.pem 2 | -------------------------------------------------------------------------------- /bin/agent/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.4.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.3.0...atm0s-reverse-proxy-agent-v0.4.0) - 2025-02-28 10 | 11 | ### Added 12 | 13 | - ssl for tcp connection (#92) 14 | 15 | ### Fixed 16 | 17 | - QUIC client should not verify hostname for adapting with multiple nodes (#94) 18 | 19 | ## [0.3.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.2.2...atm0s-reverse-proxy-agent-v0.3.0) - 2025-02-14 20 | 21 | ### Added 22 | 23 | - turn tcp/quic into features in agent, optimize client binary size (#87) 24 | 25 | ## [0.2.2](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.2.1...atm0s-reverse-proxy-agent-v0.2.2) - 2024-11-11 26 | 27 | ### Other 28 | 29 | - update Cargo.toml dependencies 30 | 31 | ## [0.2.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.2.0...atm0s-reverse-proxy-agent-v0.2.1) - 2024-10-22 32 | 33 | ### Fixed 34 | 35 | - agent id generate crash ([#74](https://github.com/8xFF/atm0s-reverse-proxy/pull/74)) 36 | 37 | ### Other 38 | 39 | - small-sdn with quic ([#70](https://github.com/8xFF/atm0s-reverse-proxy/pull/70)) 40 | 41 | ## [0.2.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.1.2...atm0s-reverse-proxy-agent-v0.2.0) - 2024-10-04 42 | 43 | ### Fixed 44 | 45 | - tunnel stuck ([#63](https://github.com/8xFF/atm0s-reverse-proxy/pull/63)) 46 | 47 | ### Other 48 | 49 | - switched to tokio ([#66](https://github.com/8xFF/atm0s-reverse-proxy/pull/66)) 50 | 51 | ## [0.1.2](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.1.1...atm0s-reverse-proxy-agent-v0.1.2) - 2024-09-23 52 | 53 | ### Fixed 54 | 55 | - sometime handshake pkt is split to some small chunks ([#60](https://github.com/8xFF/atm0s-reverse-proxy/pull/60)) 56 | 57 | ## [0.1.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-agent-v0.1.0...atm0s-reverse-proxy-agent-v0.1.1) - 2024-09-09 58 | 59 | ### Other 60 | 61 | - updated the following local packages: atm0s-reverse-proxy-protocol 62 | 63 | ## [0.1.0](https://github.com/8xFF/atm0s-reverse-proxy/releases/tag/atm0s-reverse-proxy-agent-v0.1.0) - 2024-08-17 64 | 65 | ### Added 66 | - rtsp proxy ([#37](https://github.com/8xFF/atm0s-reverse-proxy/pull/37)) 67 | - atm0s-sdn ([#2](https://github.com/8xFF/atm0s-reverse-proxy/pull/2)) 68 | - validate connection and auto generate domain from ed25519 privatekey 69 | - first working version with http and sni proxy 70 | 71 | ### Fixed 72 | - increase agent quic keep alive for reduce server load, added benchmark clients sample ([#30](https://github.com/8xFF/atm0s-reverse-proxy/pull/30)) 73 | - release action error ([#26](https://github.com/8xFF/atm0s-reverse-proxy/pull/26)) 74 | - agent quic timeout ([#19](https://github.com/8xFF/atm0s-reverse-proxy/pull/19)) 75 | - update quin for building in mipsel ([#16](https://github.com/8xFF/atm0s-reverse-proxy/pull/16)) 76 | - fixing warn and disable mips builds 77 | - subdomain too long 78 | 79 | ### Other 80 | - fix release-plz don't found default cert ([#42](https://github.com/8xFF/atm0s-reverse-proxy/pull/42)) 81 | - fix missing version for release-plz ([#41](https://github.com/8xFF/atm0s-reverse-proxy/pull/41)) 82 | - added release-plz and update deps ([#39](https://github.com/8xFF/atm0s-reverse-proxy/pull/39)) 83 | - agent log assigned domain to log ([#27](https://github.com/8xFF/atm0s-reverse-proxy/pull/27)) 84 | - update atm0s-sdn and switched to quinn forks for temporal fixing ring library ([#22](https://github.com/8xFF/atm0s-reverse-proxy/pull/22)) 85 | - fixing quinn deps in agent ([#18](https://github.com/8xFF/atm0s-reverse-proxy/pull/18)) 86 | - fixing quinn deps ([#17](https://github.com/8xFF/atm0s-reverse-proxy/pull/17)) 87 | - BREAKING CHANGE: Update newest atm0s-sdn with sans-io runtime ([#14](https://github.com/8xFF/atm0s-reverse-proxy/pull/14)) 88 | - split libs to allow customize ([#5](https://github.com/8xFF/atm0s-reverse-proxy/pull/5)) 89 | - simple quic implement 90 | - fix grammar in message 91 | - fix cli message 92 | - switch to using spawn instead of spawn_local 93 | - add more log to agent when proxy to target 94 | - optimize agent binary size 95 | - fmt 96 | -------------------------------------------------------------------------------- /bin/agent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-reverse-proxy-agent" 3 | version = "0.4.0" 4 | edition = "2021" 5 | description = "Agent for reverse proxy cluster" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | protocol = { workspace = true } 12 | protocol-ed25519 = { workspace = true, optional = true } 13 | tokio = { workspace = true, features = ["full"] } 14 | futures = { version = "0.3" } 15 | async-trait = { workspace = true } 16 | log = { workspace = true } 17 | bincode = { workspace = true } 18 | serde = { workspace = true, features = ["derive"] } 19 | url = { workspace = true } 20 | anyhow = { workspace = true } 21 | thiserror = { workspace = true, optional = true } 22 | tokio-native-tls = { workspace = true, optional = true } 23 | 24 | # for binary build 25 | picolog = { workspace = true, optional = true } 26 | argh = { workspace = true, optional = true } 27 | 28 | # for tcp protocol 29 | tokio-yamux = { workspace = true, optional = true } 30 | 31 | # for quic protocol 32 | quinn = { workspace = true, features = [ 33 | "ring", 34 | "runtime-tokio", 35 | "futures-io", 36 | ], optional = true } 37 | rustls = { workspace = true, features = ["ring", "std"], optional = true } 38 | base64 = { workspace = true, optional = true } 39 | 40 | [features] 41 | default = ["binary", "tcp", "tls", "quic"] 42 | binary = ["protocol-ed25519", "argh", "picolog"] 43 | tcp = ["tokio-yamux"] 44 | tls = ["tcp", "tokio-native-tls", "base64"] 45 | quic = ["quinn", "rustls", "base64", "thiserror"] 46 | -------------------------------------------------------------------------------- /bin/agent/benchmark_quic.sh: -------------------------------------------------------------------------------- 1 | cargo run --release --example benchmark_clients -- \ 2 | --connector-protocol quic \ 3 | --connector-addr https://127.0.0.1:13001 \ 4 | --http-dest 127.0.0.1:8080 \ 5 | --https-dest 127.0.0.1:8443 \ 6 | --allow-quic-insecure \ 7 | $@ 8 | -------------------------------------------------------------------------------- /bin/agent/benchmark_tcp.sh: -------------------------------------------------------------------------------- 1 | cargo run --example benchmark_clients -- \ 2 | --connector-protocol tcp \ 3 | --connector-addr tcp://local.ha.8xff.io:13001 \ 4 | --http-dest 127.0.0.1:8080 \ 5 | --https-dest 127.0.0.1:8443 \ 6 | $@ 7 | -------------------------------------------------------------------------------- /bin/agent/benchmark_tls.sh: -------------------------------------------------------------------------------- 1 | cargo run --example benchmark_clients -- \ 2 | --connector-protocol tls \ 3 | --connector-addr tcp://local.ha.8xff.io:13001 \ 4 | --http-dest 127.0.0.1:8080 \ 5 | --https-dest 127.0.0.1:8443 \ 6 | $@ 7 | -------------------------------------------------------------------------------- /bin/agent/examples/benchmark_clients.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::{ 3 | net::SocketAddr, 4 | sync::Arc, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use argh::FromArgs; 9 | #[cfg(feature = "quic")] 10 | use atm0s_reverse_proxy_agent::QuicConnection; 11 | #[cfg(feature = "tcp")] 12 | use atm0s_reverse_proxy_agent::TcpConnection; 13 | #[cfg(feature = "tls")] 14 | use atm0s_reverse_proxy_agent::TlsConnection; 15 | use atm0s_reverse_proxy_agent::{run_tunnel_connection, Connection, Protocol, ServiceRegistry, SimpleServiceRegistry, SubConnection}; 16 | #[cfg(any(feature = "quic", feature = "tls"))] 17 | use base64::{engine::general_purpose::URL_SAFE, Engine as _}; 18 | use log::LevelFilter; 19 | use picolog::PicoLogger; 20 | #[cfg(any(feature = "quic", feature = "tls"))] 21 | use protocol::DEFAULT_TUNNEL_CERT; 22 | use protocol_ed25519::AgentLocalKey; 23 | #[cfg(feature = "quic")] 24 | use rustls::pki_types::CertificateDer; 25 | use tokio::time::sleep; 26 | #[cfg(feature = "tls")] 27 | use tokio_native_tls::native_tls::Certificate; 28 | use url::Url; 29 | 30 | /// A benchmark util for simulating multiple clients connect to relay server 31 | #[derive(FromArgs, Debug, Clone)] 32 | struct Args { 33 | /// address of relay server 34 | #[argh(option)] 35 | connector_addr: Url, 36 | 37 | /// protocol of relay server 38 | #[argh(option)] 39 | connector_protocol: Protocol, 40 | 41 | /// http proxy dest 42 | #[argh(option, default = "SocketAddr::from_str(\"127.0.0.1:8080\").unwrap()")] 43 | http_dest: SocketAddr, 44 | 45 | /// sni-https proxy dest 46 | #[argh(option, default = "SocketAddr::from_str(\"127.0.0.1:8443\").unwrap()")] 47 | https_dest: SocketAddr, 48 | 49 | #[cfg(feature = "quic")] 50 | /// custom quic server cert in base64 51 | #[argh(option)] 52 | custom_quic_cert_base64: Option, 53 | 54 | #[cfg(feature = "quic")] 55 | /// allow connect in insecure mode 56 | #[argh(option)] 57 | allow_quic_insecure: bool, 58 | 59 | #[cfg(feature = "tls")] 60 | /// custom tls server cert in base64 61 | #[argh(option)] 62 | custom_tls_cert_base64: Option, 63 | 64 | #[cfg(feature = "tls")] 65 | /// allow connect in insecure mode 66 | #[argh(option)] 67 | allow_tls_insecure: bool, 68 | 69 | /// clients 70 | #[argh(option)] 71 | clients: usize, 72 | 73 | /// wait time between connect action 74 | #[argh(option, default = "1000")] 75 | connect_wait_ms: u64, 76 | } 77 | 78 | #[tokio::main] 79 | async fn main() { 80 | let args: Args = argh::from_env(); 81 | 82 | #[cfg(feature = "quic")] 83 | rustls::crypto::ring::default_provider().install_default().expect("should install ring as default"); 84 | 85 | //if RUST_LOG env is not set, set it to info 86 | let level = match std::env::var("RUST_LOG") { 87 | Ok(v) => LevelFilter::from_str(&v).unwrap_or(LevelFilter::Info), 88 | _ => LevelFilter::Info, 89 | }; 90 | PicoLogger::new(level).init(); 91 | 92 | let registry = SimpleServiceRegistry::new(args.http_dest, args.https_dest); 93 | let registry = Arc::new(registry); 94 | 95 | for client in 0..args.clients { 96 | let args_c = args.clone(); 97 | let registry = registry.clone(); 98 | tokio::spawn(async move { 99 | connect(client, args_c, registry).await; 100 | }); 101 | sleep(Duration::from_millis(args.connect_wait_ms)).await; 102 | } 103 | 104 | loop { 105 | sleep(Duration::from_millis(1000)).await; 106 | } 107 | } 108 | 109 | async fn connect(client: usize, args: Args, registry: Arc) { 110 | #[cfg(feature = "quic")] 111 | let default_tunnel_cert = CertificateDer::from(DEFAULT_TUNNEL_CERT.to_vec()); 112 | 113 | #[cfg(feature = "quic")] 114 | let quic_certs = if let Some(cert) = args.custom_quic_cert_base64 { 115 | vec![CertificateDer::from(URL_SAFE.decode(&cert).expect("Custom cert should in base64 format").to_vec())] 116 | } else { 117 | vec![default_tunnel_cert] 118 | }; 119 | #[cfg(feature = "tls")] 120 | let tls_cert = if let Some(cert) = args.custom_tls_cert_base64 { 121 | Certificate::from_der(&URL_SAFE.decode(cert).expect("Custom cert should in base64 format")).expect("should load custom cert") 122 | } else { 123 | Certificate::from_der(DEFAULT_TUNNEL_CERT).expect("should load default cert") 124 | }; 125 | let agent_signer = AgentLocalKey::random(); 126 | 127 | loop { 128 | log::info!("Connecting to connector... {:?} addr: {}", args.connector_protocol, args.connector_addr); 129 | let started = Instant::now(); 130 | match args.connector_protocol { 131 | #[cfg(feature = "tcp")] 132 | Protocol::Tcp => match TcpConnection::new(args.connector_addr.clone(), &agent_signer).await { 133 | Ok(conn) => { 134 | log::info!("Connected to connector via tcp with res {:?}", conn.response()); 135 | println!("{client} connected after {:?}", started.elapsed()); 136 | run_connection_loop(conn, registry.clone()).await; 137 | } 138 | Err(e) => { 139 | log::error!("Connect to connector via tcp error: {e}"); 140 | } 141 | }, 142 | #[cfg(feature = "tls")] 143 | Protocol::Tls => match TlsConnection::new(args.connector_addr.clone(), &agent_signer, tls_cert.clone(), args.allow_tls_insecure).await { 144 | Ok(conn) => { 145 | log::info!("Connected to connector via tcp with res {:?}", conn.response()); 146 | println!("{client} connected after {:?}", started.elapsed()); 147 | run_connection_loop(conn, registry.clone()).await; 148 | } 149 | Err(e) => { 150 | log::error!("Connect to connector via tcp error: {e}"); 151 | } 152 | }, 153 | #[cfg(feature = "quic")] 154 | Protocol::Quic => match QuicConnection::new(args.connector_addr.clone(), &agent_signer, &quic_certs, args.allow_quic_insecure).await { 155 | Ok(conn) => { 156 | log::info!("Connected to connector via quic with res {:?}", conn.response()); 157 | println!("{client} connected after {:?}", started.elapsed()); 158 | run_connection_loop(conn, registry.clone()).await; 159 | } 160 | Err(e) => { 161 | log::error!("Connect to connector via quic error: {e}"); 162 | } 163 | }, 164 | } 165 | //TODO exponential backoff 166 | sleep(std::time::Duration::from_secs(1)).await; 167 | } 168 | } 169 | 170 | async fn run_connection_loop(mut connection: impl Connection, registry: Arc) 171 | where 172 | S: SubConnection + 'static, 173 | { 174 | loop { 175 | match connection.recv().await { 176 | Ok(sub_connection) => { 177 | log::info!("recv sub_connection"); 178 | let registry = registry.clone(); 179 | tokio::spawn(run_tunnel_connection(sub_connection, registry)); 180 | } 181 | Err(e) => { 182 | log::error!("recv sub_connection error: {}", e); 183 | break; 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /bin/agent/node_local_quic.sh: -------------------------------------------------------------------------------- 1 | RUST_LOG=info cargo run --features quic -- \ 2 | --connector-protocol quic \ 3 | --connector-addr https://127.0.0.1:13001 \ 4 | --http-dest 127.0.0.1:8080 \ 5 | --https-dest 127.0.0.1:8443 \ 6 | --rtsp-dest 10.10.30.90:554 7 | -------------------------------------------------------------------------------- /bin/agent/node_local_tcp.sh: -------------------------------------------------------------------------------- 1 | RUST_LOG=info cargo run -- \ 2 | --connector-protocol tcp \ 3 | --connector-addr tcp://127.0.0.1:23001 \ 4 | --http-dest 127.0.0.1:8080 \ 5 | --https-dest 127.0.0.1:8443 \ 6 | --rtsp-dest 10.10.30.90:554 7 | -------------------------------------------------------------------------------- /bin/agent/node_local_tls.sh: -------------------------------------------------------------------------------- 1 | RUST_LOG=info cargo run -- \ 2 | --connector-protocol tls \ 3 | --connector-addr tls://127.0.0.1:13001 \ 4 | --http-dest 127.0.0.1:8080 \ 5 | --https-dest 127.0.0.1:8443 \ 6 | --rtsp-dest 10.10.30.90:554 7 | -------------------------------------------------------------------------------- /bin/agent/src/connection.rs: -------------------------------------------------------------------------------- 1 | //! Tunnel is a trait that defines the interface for a tunnel which connect to connector port of relayer. 2 | 3 | use std::{fmt::Debug, str::FromStr}; 4 | 5 | use protocol::stream::TunnelStream; 6 | use tokio::io::{AsyncRead, AsyncWrite}; 7 | 8 | #[cfg(feature = "quic")] 9 | pub mod quic; 10 | 11 | #[cfg(feature = "tcp")] 12 | pub mod tcp; 13 | 14 | #[cfg(feature = "tls")] 15 | pub mod tls; 16 | 17 | #[derive(Debug, Clone)] 18 | pub enum Protocol { 19 | #[cfg(feature = "tcp")] 20 | Tcp, 21 | #[cfg(feature = "tls")] 22 | Tls, 23 | #[cfg(feature = "quic")] 24 | Quic, 25 | } 26 | 27 | impl FromStr for Protocol { 28 | type Err = &'static str; 29 | fn from_str(s: &str) -> Result { 30 | match s { 31 | #[cfg(feature = "tcp")] 32 | "tcp" | "TCP" => Ok(Protocol::Tcp), 33 | #[cfg(feature = "tls")] 34 | "tls" | "TLS" => Ok(Protocol::Tls), 35 | #[cfg(feature = "quic")] 36 | "quic" | "QUIC" => Ok(Protocol::Quic), 37 | _ => Err("invalid protocol"), 38 | } 39 | } 40 | } 41 | 42 | pub trait SubConnection: AsyncRead + AsyncWrite + Unpin + Send + Sync {} 43 | 44 | impl SubConnection for TunnelStream {} 45 | 46 | #[async_trait::async_trait] 47 | pub trait Connection: Send + Sync { 48 | async fn create_outgoing(&mut self) -> anyhow::Result; 49 | async fn recv(&mut self) -> anyhow::Result; 50 | } 51 | -------------------------------------------------------------------------------- /bin/agent/src/connection/quic.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use helper::configure_client; 3 | use protocol::{key::AgentSigner, stream::TunnelStream}; 4 | use rustls::pki_types::CertificateDer; 5 | use serde::de::DeserializeOwned; 6 | use std::net::ToSocketAddrs; 7 | use url::Url; 8 | 9 | use quinn::{Endpoint, RecvStream, SendStream}; 10 | 11 | mod helper; 12 | mod no_servername_verify; 13 | 14 | use super::Connection; 15 | 16 | pub type QuicSubConnection = TunnelStream; 17 | 18 | pub struct QuicConnection { 19 | response: RES, 20 | connection: quinn::Connection, 21 | } 22 | 23 | impl QuicConnection { 24 | pub async fn new>(url: Url, agent_signer: &AS, server_certs: &[CertificateDer<'static>], allow_quic_insecure: bool) -> anyhow::Result { 25 | let url_host = url.host_str().ok_or(anyhow!("InvalidUrl"))?; 26 | let url_port = url.port().unwrap_or(33333); 27 | log::info!("connecting to server {}:{}", url_host, url_port); 28 | let remote = (url_host, url_port).to_socket_addrs()?.next().ok_or(anyhow!("DnsError"))?; 29 | 30 | let mut endpoint = Endpoint::client("0.0.0.0:0".parse().expect("Should parse local addr"))?; 31 | endpoint.set_default_client_config(configure_client(server_certs, allow_quic_insecure)?); 32 | 33 | // connect to server 34 | let connection = endpoint.connect(remote, url_host)?.await?; 35 | 36 | log::info!("connected to {}, open bi stream", url); 37 | let (mut send_stream, mut recv_stream) = connection.open_bi().await?; 38 | log::info!("opened bi stream, send register request"); 39 | 40 | send_stream.write_all(&agent_signer.sign_connect_req()).await?; 41 | 42 | let mut buf = [0u8; 4096]; 43 | let buf_len = recv_stream.read(&mut buf).await?.ok_or(anyhow!("NoData"))?; 44 | let response: RES = agent_signer.validate_connect_res(&buf[..buf_len]).map_err(|e| anyhow!("validate connect rest error {e}"))?; 45 | Ok(Self { connection, response }) 46 | } 47 | 48 | pub fn response(&self) -> &RES { 49 | &self.response 50 | } 51 | } 52 | 53 | #[async_trait::async_trait] 54 | impl Connection for QuicConnection { 55 | async fn create_outgoing(&mut self) -> anyhow::Result { 56 | let (send, recv) = self.connection.open_bi().await?; 57 | Ok(QuicSubConnection::new(recv, send)) 58 | } 59 | 60 | async fn recv(&mut self) -> anyhow::Result { 61 | let (send, recv) = self.connection.accept_bi().await?; 62 | Ok(QuicSubConnection::new(recv, send)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /bin/agent/src/connection/quic/helper.rs: -------------------------------------------------------------------------------- 1 | use rustls::pki_types::CertificateDer; 2 | use std::{sync::Arc, time::Duration}; 3 | use thiserror::Error; 4 | 5 | use quinn::{ 6 | crypto::rustls::{NoInitialCipherSuite, QuicClientConfig}, 7 | ClientConfig, TransportConfig, 8 | }; 9 | 10 | use super::no_servername_verify::NoServerNameVerification; 11 | 12 | #[derive(Debug, Error)] 13 | pub enum QuicTlsError { 14 | #[error("NoInitialCipherSuite")] 15 | NoInitialCipherSuite(#[from] NoInitialCipherSuite), 16 | #[error("Rustls")] 17 | Rustls(#[from] rustls::Error), 18 | #[error("VerifierBuilderError")] 19 | VerifierBuilderError(#[from] rustls::client::VerifierBuilderError), 20 | } 21 | 22 | pub fn configure_client(server_certs: &[CertificateDer], allow_quic_insecure: bool) -> Result { 23 | let mut root = rustls::RootCertStore::empty(); 24 | for cert in server_certs { 25 | root.add(cert.clone())?; 26 | } 27 | 28 | let client = rustls::ClientConfig::builder() 29 | .dangerous() 30 | .with_custom_certificate_verifier(Arc::new(NoServerNameVerification::new(root, allow_quic_insecure)?)) 31 | .with_no_client_auth(); 32 | let mut config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(client)?)); 33 | 34 | let mut transport = TransportConfig::default(); 35 | transport.keep_alive_interval(Some(Duration::from_secs(15))); 36 | transport.max_idle_timeout(Some(Duration::from_secs(30).try_into().expect("Should config timeout"))); 37 | config.transport_config(Arc::new(transport)); 38 | Ok(config) 39 | } 40 | -------------------------------------------------------------------------------- /bin/agent/src/connection/quic/no_servername_verify.rs: -------------------------------------------------------------------------------- 1 | // this code is copy from https://gist.github.com/doroved/2c92ddd5e33f257f901c763b728d1b61 2 | use rustls::{ 3 | client::{ 4 | danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, 5 | WebPkiServerVerifier, 6 | }, 7 | pki_types::{CertificateDer, ServerName, UnixTime}, 8 | server::VerifierBuilderError, 9 | DigitallySignedStruct, RootCertStore, 10 | }; 11 | use std::sync::Arc; 12 | 13 | #[derive(Debug)] 14 | pub struct NoServerNameVerification { 15 | inner: Arc, 16 | allow_quic_insecure: bool, 17 | } 18 | 19 | impl NoServerNameVerification { 20 | pub fn new(root: RootCertStore, allow_quic_insecure: bool) -> Result { 21 | Ok(Self { 22 | inner: rustls::client::WebPkiServerVerifier::builder(Arc::new(root)).build()?, 23 | allow_quic_insecure, 24 | }) 25 | } 26 | } 27 | 28 | impl ServerCertVerifier for NoServerNameVerification { 29 | fn verify_server_cert( 30 | &self, 31 | _end_entity: &CertificateDer<'_>, 32 | _intermediates: &[CertificateDer<'_>], 33 | _server_name: &ServerName<'_>, 34 | _ocsp: &[u8], 35 | _now: UnixTime, 36 | ) -> Result { 37 | if self.allow_quic_insecure { 38 | return Ok(ServerCertVerified::assertion()); 39 | } 40 | 41 | match self.inner.verify_server_cert(_end_entity, _intermediates, _server_name, _ocsp, _now) { 42 | Ok(scv) => Ok(scv), 43 | Err(rustls::Error::InvalidCertificate(cert_error)) => { 44 | if let rustls::CertificateError::NotValidForName = cert_error { 45 | Ok(ServerCertVerified::assertion()) 46 | } else { 47 | Err(rustls::Error::InvalidCertificate(cert_error)) 48 | } 49 | } 50 | Err(e) => Err(e), 51 | } 52 | } 53 | 54 | fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct) -> Result { 55 | self.inner.verify_tls12_signature(message, cert, dss) 56 | } 57 | 58 | fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct) -> Result { 59 | self.inner.verify_tls13_signature(message, cert, dss) 60 | } 61 | 62 | fn supported_verify_schemes(&self) -> Vec { 63 | self.inner.supported_verify_schemes() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bin/agent/src/connection/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::net::ToSocketAddrs; 2 | 3 | use super::{Connection, SubConnection}; 4 | use anyhow::anyhow; 5 | use futures::prelude::*; 6 | use protocol::key::AgentSigner; 7 | use serde::de::DeserializeOwned; 8 | use tokio::{ 9 | io::{AsyncReadExt, AsyncWriteExt}, 10 | net::TcpStream, 11 | }; 12 | use tokio_yamux::{Session, StreamHandle}; 13 | use url::Url; 14 | 15 | pub type TcpSubConnection = StreamHandle; 16 | 17 | impl SubConnection for StreamHandle {} 18 | 19 | pub struct TcpConnection { 20 | response: RES, 21 | session: Session, 22 | } 23 | 24 | impl TcpConnection { 25 | pub async fn new>(url: Url, agent_signer: &AS) -> anyhow::Result { 26 | let url_host = url.host_str().ok_or(anyhow!("couldn't get host from url"))?; 27 | let url_port = url.port().unwrap_or(33333); 28 | log::info!("connecting to server {}:{}", url_host, url_port); 29 | let remote = (url_host, url_port).to_socket_addrs()?.next().ok_or(anyhow!("couldn't resolve to an address"))?; 30 | 31 | let mut stream = TcpStream::connect(remote).await?; 32 | stream.write_all(&agent_signer.sign_connect_req()).await?; 33 | 34 | let mut buf = [0u8; 4096]; 35 | let buf_len = stream.read(&mut buf).await?; 36 | let response: RES = agent_signer.validate_connect_res(&buf[..buf_len]).map_err(|e| anyhow!("{e}"))?; 37 | Ok(Self { 38 | session: Session::new_server(stream, Default::default()), 39 | response, 40 | }) 41 | } 42 | 43 | pub fn response(&self) -> &RES { 44 | &self.response 45 | } 46 | } 47 | 48 | #[async_trait::async_trait] 49 | impl Connection for TcpConnection { 50 | async fn create_outgoing(&mut self) -> anyhow::Result { 51 | let stream = self.session.open_stream()?; 52 | Ok(stream) 53 | } 54 | 55 | async fn recv(&mut self) -> anyhow::Result { 56 | let stream = self.session.next().await.ok_or(anyhow!("accept new connection error"))??; 57 | Ok(stream) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /bin/agent/src/connection/tls.rs: -------------------------------------------------------------------------------- 1 | use std::net::ToSocketAddrs; 2 | 3 | use anyhow::anyhow; 4 | use futures::StreamExt; 5 | use protocol::key::AgentSigner; 6 | use serde::de::DeserializeOwned; 7 | use tokio::{ 8 | io::{AsyncReadExt, AsyncWriteExt}, 9 | net::TcpStream, 10 | }; 11 | use tokio_native_tls::{native_tls::Certificate, TlsConnector, TlsStream}; 12 | use tokio_yamux::Session; 13 | use url::Url; 14 | 15 | use crate::TcpSubConnection; 16 | 17 | use super::Connection; 18 | 19 | pub struct TlsConnection { 20 | response: RES, 21 | session: Session>, 22 | } 23 | 24 | impl TlsConnection { 25 | pub async fn new>(url: Url, agent_signer: &AS, rootca: Certificate, allow_unsecure: bool) -> anyhow::Result { 26 | let url_host = url.host_str().ok_or(anyhow!("couldn't get host from url"))?; 27 | let url_port = url.port().unwrap_or(33333); 28 | 29 | log::info!("connecting to server {}:{}", url_host, url_port); 30 | let remote = (url_host, url_port).to_socket_addrs()?.next().ok_or(anyhow!("couldn't resolve to an address"))?; 31 | let stream = TcpStream::connect(remote).await?; 32 | 33 | let connector_builder = tokio_native_tls::native_tls::TlsConnector::builder() 34 | .danger_accept_invalid_hostnames(true) 35 | .danger_accept_invalid_certs(allow_unsecure) 36 | .add_root_certificate(rootca) 37 | .build()?; 38 | let connector = TlsConnector::from(connector_builder); 39 | 40 | let mut tls_stream = connector.connect(url_host, stream).await?; 41 | 42 | tls_stream.write_all(&agent_signer.sign_connect_req()).await?; 43 | 44 | let mut buf = [0u8; 4096]; 45 | let buf_len = tls_stream.read(&mut buf).await?; 46 | let response: RES = agent_signer.validate_connect_res(&buf[..buf_len]).map_err(|e| anyhow!("{e}"))?; 47 | Ok(Self { 48 | session: Session::new_server(tls_stream, Default::default()), 49 | response, 50 | }) 51 | } 52 | 53 | pub fn response(&self) -> &RES { 54 | &self.response 55 | } 56 | } 57 | 58 | #[async_trait::async_trait] 59 | impl Connection for TlsConnection { 60 | async fn create_outgoing(&mut self) -> anyhow::Result { 61 | let stream = self.session.open_stream()?; 62 | Ok(stream) 63 | } 64 | 65 | async fn recv(&mut self) -> anyhow::Result { 66 | let stream = self.session.next().await.ok_or(anyhow!("accept new connection error"))??; 67 | Ok(stream) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /bin/agent/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use protocol::cluster::{wait_object, AgentTunnelRequest}; 4 | 5 | mod connection; 6 | mod local_tunnel; 7 | #[cfg(feature = "quic")] 8 | pub use connection::quic::{QuicConnection, QuicSubConnection}; 9 | #[cfg(feature = "tcp")] 10 | pub use connection::{ 11 | tcp::{TcpConnection, TcpSubConnection}, 12 | tls::TlsConnection, 13 | }; 14 | 15 | pub use connection::{Connection, Protocol, SubConnection}; 16 | pub use local_tunnel::{registry::SimpleServiceRegistry, LocalTunnel, ServiceRegistry}; 17 | use tokio::{io::copy_bidirectional, net::TcpStream}; 18 | 19 | pub async fn run_tunnel_connection(mut incoming_proxy_conn: S, registry: Arc) 20 | where 21 | S: SubConnection, 22 | { 23 | log::info!("sub_connection pipe to local_tunnel start"); 24 | 25 | let local_tunnel = match wait_object::<_, AgentTunnelRequest, 1000>(&mut incoming_proxy_conn).await { 26 | Ok(handshake) => { 27 | log::info!("sub_connection pipe with handshake: tls: {}, {}/{:?} ", handshake.tls, handshake.domain, handshake.service); 28 | if let Some(dest) = registry.dest_for(handshake.tls, handshake.service, &handshake.domain) { 29 | log::info!("create tunnel to dest {}", dest); 30 | TcpStream::connect(dest).await 31 | } else { 32 | log::warn!("dest for service {:?} tls {} domain {} not found", handshake.service, handshake.tls, handshake.domain); 33 | return; 34 | } 35 | } 36 | Err(e) => { 37 | log::error!("read first pkt error: {}", e); 38 | return; 39 | } 40 | }; 41 | 42 | let mut local_tunnel = match local_tunnel { 43 | Ok(local_tunnel) => local_tunnel, 44 | Err(e) => { 45 | log::error!("create local_tunnel error: {}", e); 46 | return; 47 | } 48 | }; 49 | 50 | log::info!("sub_connection pipe to local_tunnel start"); 51 | 52 | match copy_bidirectional(&mut incoming_proxy_conn, &mut local_tunnel).await { 53 | Ok(res) => { 54 | log::info!("sub_connection pipe to local_tunnel stop res {res:?}"); 55 | } 56 | Err(e) => { 57 | log::error!("sub_connection pipe to local_tunnel stop err {e:?}"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /bin/agent/src/local_tunnel.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use tokio::io::{AsyncRead, AsyncWrite}; 4 | 5 | pub mod registry; 6 | pub mod tcp; 7 | 8 | pub trait LocalTunnel: AsyncRead + AsyncWrite + Unpin + Send + Sync {} 9 | 10 | pub trait ServiceRegistry: Send + Sync { 11 | fn dest_for(&self, tls: bool, service: Option, domain: &str) -> Option; 12 | } 13 | -------------------------------------------------------------------------------- /bin/agent/src/local_tunnel/registry.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, net::SocketAddr}; 2 | 3 | use super::ServiceRegistry; 4 | 5 | pub struct SimpleServiceRegistry { 6 | default_tcp: SocketAddr, 7 | default_tls: SocketAddr, 8 | tcp_services: HashMap, 9 | tls_services: HashMap, 10 | } 11 | 12 | impl SimpleServiceRegistry { 13 | pub fn new(default_tcp: SocketAddr, default_tls: SocketAddr) -> Self { 14 | Self { 15 | default_tcp, 16 | default_tls, 17 | tcp_services: HashMap::new(), 18 | tls_services: HashMap::new(), 19 | } 20 | } 21 | 22 | pub fn set_tcp_service(&mut self, service: u16, dest: SocketAddr) { 23 | self.tcp_services.insert(service, dest); 24 | } 25 | 26 | pub fn set_tls_service(&mut self, service: u16, dest: SocketAddr) { 27 | self.tls_services.insert(service, dest); 28 | } 29 | } 30 | 31 | impl ServiceRegistry for SimpleServiceRegistry { 32 | fn dest_for(&self, tls: bool, service: Option, _domain: &str) -> Option { 33 | match (tls, service) { 34 | (false, None) => Some(self.default_tcp), 35 | (true, None) => Some(self.default_tls), 36 | (false, Some(service)) => self.tcp_services.get(&service).cloned(), 37 | (true, Some(service)) => self.tls_services.get(&service).cloned(), 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bin/agent/src/local_tunnel/tcp.rs: -------------------------------------------------------------------------------- 1 | use tokio::net::TcpStream; 2 | 3 | use super::LocalTunnel; 4 | 5 | impl LocalTunnel for TcpStream {} 6 | -------------------------------------------------------------------------------- /bin/agent/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::{alloc::System, net::SocketAddr, sync::Arc}; 3 | 4 | use log::LevelFilter; 5 | use picolog::PicoLogger; 6 | 7 | #[cfg(feature = "quic")] 8 | use atm0s_reverse_proxy_agent::QuicConnection; 9 | #[cfg(feature = "tls")] 10 | use atm0s_reverse_proxy_agent::TlsConnection; 11 | use atm0s_reverse_proxy_agent::{run_tunnel_connection, Connection, Protocol, ServiceRegistry, SimpleServiceRegistry, SubConnection, TcpConnection}; 12 | #[cfg(any(feature = "quic", feature = "tls"))] 13 | use base64::{engine::general_purpose::URL_SAFE, Engine as _}; 14 | 15 | use argh::FromArgs; 16 | use protocol::services::SERVICE_RTSP; 17 | #[cfg(any(feature = "quic", feature = "tls"))] 18 | use protocol::DEFAULT_TUNNEL_CERT; 19 | use protocol_ed25519::AgentLocalKey; 20 | #[cfg(feature = "quic")] 21 | use rustls::pki_types::CertificateDer; 22 | use tokio::time::sleep; 23 | #[cfg(feature = "tls")] 24 | use tokio_native_tls::native_tls::Certificate; 25 | use url::Url; 26 | 27 | #[global_allocator] 28 | static A: System = System; 29 | 30 | /// A HTTP and SNI HTTPs proxy for expose your local service to the internet. 31 | #[derive(FromArgs, Debug)] 32 | struct Args { 33 | /// address of relay server 34 | #[argh(option)] 35 | connector_addr: Url, 36 | 37 | /// protocol of relay server 38 | #[argh(option)] 39 | connector_protocol: Protocol, 40 | 41 | /// http proxy dest 42 | #[argh(option, default = "SocketAddr::from_str(\"127.0.0.1:8080\").unwrap()")] 43 | http_dest: SocketAddr, 44 | 45 | /// sni-https proxy dest 46 | #[argh(option, default = "SocketAddr::from_str(\"127.0.0.1:8443\").unwrap()")] 47 | https_dest: SocketAddr, 48 | 49 | /// rtsp proxy dest 50 | #[argh(option, default = "SocketAddr::from_str(\"127.0.0.1:554\").unwrap()")] 51 | rtsp_dest: SocketAddr, 52 | 53 | /// sni-https proxy dest 54 | #[argh(option, default = "SocketAddr::from_str(\"127.0.0.1:5443\").unwrap()")] 55 | rtsps_dest: SocketAddr, 56 | 57 | /// persistent local key 58 | #[argh(option, default = "String::from(\"local_key.pem\")")] 59 | local_key: String, 60 | 61 | #[cfg(feature = "quic")] 62 | /// custom quic server cert in base64 63 | #[argh(option)] 64 | custom_quic_cert_base64: Option, 65 | 66 | #[cfg(feature = "quic")] 67 | /// allow connect in insecure mode 68 | #[argh(switch)] 69 | allow_quic_insecure: bool, 70 | 71 | #[cfg(feature = "tls")] 72 | /// custom tls server cert in base64 73 | #[argh(option)] 74 | custom_tls_cert_base64: Option, 75 | 76 | #[cfg(feature = "tls")] 77 | /// allow connect in insecure mode 78 | #[argh(switch)] 79 | allow_tls_insecure: bool, 80 | } 81 | 82 | #[tokio::main] 83 | async fn main() { 84 | let args: Args = argh::from_env(); 85 | //if RUST_LOG env is not set, set it to info 86 | let level = match std::env::var("RUST_LOG") { 87 | Ok(v) => LevelFilter::from_str(&v).unwrap_or(LevelFilter::Info), 88 | _ => LevelFilter::Info, 89 | }; 90 | PicoLogger::new(level).init(); 91 | 92 | #[cfg(feature = "quic")] 93 | let quic_certs = if let Some(cert) = args.custom_quic_cert_base64 { 94 | vec![CertificateDer::from(URL_SAFE.decode(cert).expect("Custom cert should in base64 format").to_vec())] 95 | } else { 96 | vec![CertificateDer::from(DEFAULT_TUNNEL_CERT.to_vec())] 97 | }; 98 | 99 | #[cfg(feature = "tls")] 100 | let tls_cert = if let Some(cert) = args.custom_tls_cert_base64 { 101 | Certificate::from_der(&URL_SAFE.decode(cert).expect("Custom cert should in base64 format")).expect("should load custom cert") 102 | } else { 103 | Certificate::from_der(DEFAULT_TUNNEL_CERT).expect("should load default cert") 104 | }; 105 | 106 | #[cfg(feature = "quic")] 107 | rustls::crypto::ring::default_provider().install_default().expect("should install ring as default"); 108 | 109 | //read local_key from file first, if not exist, create a new one and save to file 110 | let agent_signer = match std::fs::read_to_string(&args.local_key) { 111 | Ok(local_key) => match AgentLocalKey::from_pem(&local_key) { 112 | Some(local_key) => { 113 | log::info!("loadded local_key: \n{}", local_key.to_pem()); 114 | local_key 115 | } 116 | None => { 117 | log::error!("read local_key from file error: invalid pem"); 118 | return; 119 | } 120 | }, 121 | Err(e) => { 122 | //check if file not exist 123 | if e.kind() != std::io::ErrorKind::NotFound { 124 | log::error!("read local_key from file error: {}", e); 125 | return; 126 | } 127 | 128 | log::warn!("local_key file not found => regenerate"); 129 | let local_key = AgentLocalKey::random(); 130 | log::info!("created local_key: \n{}", local_key.to_pem()); 131 | if let Err(e) = std::fs::write(&args.local_key, local_key.to_pem()) { 132 | log::error!("write local_key to file error: {}", e); 133 | return; 134 | } 135 | local_key 136 | } 137 | }; 138 | 139 | let mut registry = SimpleServiceRegistry::new(args.http_dest, args.https_dest); 140 | registry.set_tcp_service(SERVICE_RTSP, args.rtsp_dest); 141 | registry.set_tls_service(SERVICE_RTSP, args.rtsps_dest); 142 | let registry = Arc::new(registry); 143 | 144 | loop { 145 | log::info!("Connecting to connector... {:?} addr: {}", args.connector_protocol, args.connector_addr); 146 | match args.connector_protocol { 147 | #[cfg(feature = "tcp")] 148 | Protocol::Tcp => match TcpConnection::new(args.connector_addr.clone(), &agent_signer).await { 149 | Ok(conn) => { 150 | log::info!("Connected to connector via tcp with res {:?}", conn.response()); 151 | run_connection_loop(conn, registry.clone()).await; 152 | } 153 | Err(e) => { 154 | log::error!("Connect to connector via tcp error: {e}"); 155 | } 156 | }, 157 | #[cfg(feature = "tls")] 158 | Protocol::Tls => match TlsConnection::new(args.connector_addr.clone(), &agent_signer, tls_cert.clone(), args.allow_tls_insecure).await { 159 | Ok(conn) => { 160 | log::info!("Connected to connector via tcp with res {:?}", conn.response()); 161 | run_connection_loop(conn, registry.clone()).await; 162 | } 163 | Err(e) => { 164 | log::error!("Connect to connector via tcp error: {e}"); 165 | } 166 | }, 167 | #[cfg(feature = "quic")] 168 | Protocol::Quic => match QuicConnection::new(args.connector_addr.clone(), &agent_signer, &quic_certs, args.allow_quic_insecure).await { 169 | Ok(conn) => { 170 | log::info!("Connected to connector via quic with res {:?}", conn.response()); 171 | run_connection_loop(conn, registry.clone()).await; 172 | } 173 | Err(e) => { 174 | log::error!("Connect to connector via quic error: {e}"); 175 | } 176 | }, 177 | } 178 | //TODO exponential backoff 179 | sleep(std::time::Duration::from_secs(1)).await; 180 | } 181 | } 182 | 183 | pub async fn run_connection_loop(mut connection: impl Connection, registry: Arc) 184 | where 185 | S: SubConnection + 'static, 186 | { 187 | loop { 188 | match connection.recv().await { 189 | Ok(sub_connection) => { 190 | log::info!("recv sub_connection"); 191 | let registry = registry.clone(); 192 | tokio::spawn(run_tunnel_connection(sub_connection, registry)); 193 | } 194 | Err(e) => { 195 | log::error!("recv sub_connection error: {}", e); 196 | break; 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /bin/relayer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.6.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.6.0...atm0s-reverse-proxy-relayer-v0.6.1) - 2025-03-03 10 | 11 | ### Fixed 12 | 13 | - tls server recv function shouldn't contain await ([#96](https://github.com/8xFF/atm0s-reverse-proxy/pull/96)) 14 | 15 | ## [0.6.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.5.0...atm0s-reverse-proxy-relayer-v0.6.0) - 2025-02-28 16 | 17 | ### Added 18 | 19 | - ssl for tcp connection (#92) 20 | 21 | ## [0.5.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.4.4...atm0s-reverse-proxy-relayer-v0.5.0) - 2025-02-14 22 | 23 | ### Added 24 | 25 | - add agent context uses in forward from agent to service (#90) 26 | 27 | ## [0.4.4](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.4.3...atm0s-reverse-proxy-relayer-v0.4.4) - 2024-12-18 28 | 29 | ### Other 30 | 31 | - update Cargo.toml dependencies 32 | 33 | ## [0.4.3](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.4.2...atm0s-reverse-proxy-relayer-v0.4.3) - 2024-11-26 34 | 35 | ### Other 36 | 37 | - update Cargo.toml dependencies 38 | 39 | ## [0.4.2](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.4.1...atm0s-reverse-proxy-relayer-v0.4.2) - 2024-11-11 40 | 41 | ### Other 42 | 43 | - update p2p lib for fixing alias issue ([#79](https://github.com/8xFF/atm0s-reverse-proxy/pull/79)) 44 | 45 | ## [0.4.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.4.0...atm0s-reverse-proxy-relayer-v0.4.1) - 2024-11-07 46 | 47 | ### Fixed 48 | 49 | - alias not release bug ([#77](https://github.com/8xFF/atm0s-reverse-proxy/pull/77)) 50 | 51 | ## [0.4.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.3.0...atm0s-reverse-proxy-relayer-v0.4.0) - 2024-10-22 52 | 53 | ### Added 54 | 55 | - add metrics for relayer ([#72](https://github.com/8xFF/atm0s-reverse-proxy/pull/72)) 56 | - update sdn for network connect authorizing ([#73](https://github.com/8xFF/atm0s-reverse-proxy/pull/73)) 57 | 58 | ### Fixed 59 | 60 | - agent id generate crash ([#74](https://github.com/8xFF/atm0s-reverse-proxy/pull/74)) 61 | 62 | ### Other 63 | 64 | - small-sdn with quic ([#70](https://github.com/8xFF/atm0s-reverse-proxy/pull/70)) 65 | 66 | ## [0.3.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.2.0...atm0s-reverse-proxy-relayer-v0.3.0) - 2024-10-04 67 | 68 | ### Other 69 | 70 | - update deps and switch to parking_lot ([#68](https://github.com/8xFF/atm0s-reverse-proxy/pull/68)) 71 | 72 | ## [0.2.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.1.2...atm0s-reverse-proxy-relayer-v0.2.0) - 2024-10-04 73 | 74 | ### Fixed 75 | 76 | - tunnel stuck ([#63](https://github.com/8xFF/atm0s-reverse-proxy/pull/63)) 77 | - fix remove worker after instantly agent is reconnected ([#62](https://github.com/8xFF/atm0s-reverse-proxy/pull/62)) 78 | 79 | ### Other 80 | 81 | - switched to tokio ([#66](https://github.com/8xFF/atm0s-reverse-proxy/pull/66)) 82 | 83 | ## [0.1.2](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.1.1...atm0s-reverse-proxy-relayer-v0.1.2) - 2024-09-10 84 | 85 | ### Fixed 86 | 87 | - add node_info struct to feedback status to sdn collector ([#52](https://github.com/8xFF/atm0s-reverse-proxy/pull/52)) 88 | 89 | ## [0.1.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-relayer-v0.1.0...atm0s-reverse-proxy-relayer-v0.1.1) - 2024-09-09 90 | 91 | ### Fixed 92 | 93 | - wrong handshake when tunnel request over cluster ([#48](https://github.com/8xFF/atm0s-reverse-proxy/pull/48)) 94 | 95 | ### Other 96 | 97 | - fix typo in CHANGELOG ([#45](https://github.com/8xFF/atm0s-reverse-proxy/pull/45)) 98 | 99 | ## [0.1.0](https://github.com/8xFF/atm0s-reverse-proxy/releases/tag/atm0s-reverse-proxy-relayer-v0.1.0) - 2024-08-17 100 | 101 | ### Added 102 | - rtsp proxy ([#37](https://github.com/8xFF/atm0s-reverse-proxy/pull/37)) 103 | - metrics outgoing cluster ([#34](https://github.com/8xFF/atm0s-reverse-proxy/pull/34)) 104 | - allow dynamic root domain, only check first sub-domain part ([#4](https://github.com/8xFF/atm0s-reverse-proxy/pull/4)) 105 | - atm0s-sdn ([#2](https://github.com/8xFF/atm0s-reverse-proxy/pull/2)) 106 | - add simple dashboard 107 | - validate connection and auto generate domain from ed25519 privatekey 108 | - first working version with http and sni proxy 109 | 110 | ### Fixed 111 | - using spawn task outside run_agent_connection to wait when agent is disconnected ([#38](https://github.com/8xFF/atm0s-reverse-proxy/pull/38)) 112 | - histograms metrics to seconds ([#36](https://github.com/8xFF/atm0s-reverse-proxy/pull/36)) 113 | - fixed histogram metrics not working ([#35](https://github.com/8xFF/atm0s-reverse-proxy/pull/35)) 114 | - don't blocking proxy request from agent, refactor metrics ([#33](https://github.com/8xFF/atm0s-reverse-proxy/pull/33)) 115 | - deadlock in agents map => move agents map to separated struct AgentStorage for avoiding block ([#32](https://github.com/8xFF/atm0s-reverse-proxy/pull/32)) 116 | - quic_listener will stuck if have huge of waiting incoming conns and cause timeout ([#31](https://github.com/8xFF/atm0s-reverse-proxy/pull/31)) 117 | - increase agent quic keep alive for reduce server load, added benchmark clients sample ([#30](https://github.com/8xFF/atm0s-reverse-proxy/pull/30)) 118 | - release action error ([#26](https://github.com/8xFF/atm0s-reverse-proxy/pull/26)) 119 | - prometheus metric wrong format, use \_ instead of \. ([#24](https://github.com/8xFF/atm0s-reverse-proxy/pull/24)) 120 | - virtual socket memory leak, virtual socket port request safety ([#23](https://github.com/8xFF/atm0s-reverse-proxy/pull/23)) 121 | - agent quic timeout ([#19](https://github.com/8xFF/atm0s-reverse-proxy/pull/19)) 122 | - update quin for building in mipsel ([#16](https://github.com/8xFF/atm0s-reverse-proxy/pull/16)) 123 | - wrong check domain when proxy inside haproxy ([#3](https://github.com/8xFF/atm0s-reverse-proxy/pull/3)) 124 | - fixing warn and disable mips builds 125 | - crash on parse invalid sni 126 | - wrong ymux mode in relay agent connection 127 | 128 | ### Other 129 | - fix release-plz don't found default cert ([#42](https://github.com/8xFF/atm0s-reverse-proxy/pull/42)) 130 | - fix missing version for release-plz ([#41](https://github.com/8xFF/atm0s-reverse-proxy/pull/41)) 131 | - added release-plz and update deps ([#39](https://github.com/8xFF/atm0s-reverse-proxy/pull/39)) 132 | - update metrics and metrics-dashboard version ([#29](https://github.com/8xFF/atm0s-reverse-proxy/pull/29)) 133 | - update atm0s-sdn and switched to quinn forks for temporal fixing ring library ([#22](https://github.com/8xFF/atm0s-reverse-proxy/pull/22)) 134 | - update atm0s-sdn with new authorization and encryption features ([#21](https://github.com/8xFF/atm0s-reverse-proxy/pull/21)) 135 | - update sdn for fixing some alias error ([#20](https://github.com/8xFF/atm0s-reverse-proxy/pull/20)) 136 | - fixing quinn deps ([#17](https://github.com/8xFF/atm0s-reverse-proxy/pull/17)) 137 | - expose atm0s-sdn and some functions to public ([#15](https://github.com/8xFF/atm0s-reverse-proxy/pull/15)) 138 | - BREAKING CHANGE: Update newest atm0s-sdn with sans-io runtime ([#14](https://github.com/8xFF/atm0s-reverse-proxy/pull/14)) 139 | - split libs to allow customize ([#5](https://github.com/8xFF/atm0s-reverse-proxy/pull/5)) 140 | - simple quic implement 141 | - switch expose metrics to optional 142 | - switch to using spawn instead of spawn_local 143 | - fmt 144 | -------------------------------------------------------------------------------- /bin/relayer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-reverse-proxy-relayer" 3 | version = "0.6.1" 4 | edition = "2021" 5 | description = "Server for atm0s-reverse proxy cluster" 6 | license = "MIT" 7 | 8 | [dependencies] 9 | p2p = { workspace = true } 10 | protocol = { workspace = true } 11 | protocol-ed25519 = { workspace = true, optional = true } 12 | metrics-dashboard = { workspace = true, features = ["system"], optional = true } 13 | tokio = { workspace = true, features = ["full"] } 14 | clap = { workspace = true, features = ["derive", "env", "color"] } 15 | quinn = { workspace = true, features = ["ring", "runtime-tokio", "futures-io"] } 16 | rustls = { workspace = true, features = ["ring", "std"] } 17 | anyhow = { workspace = true } 18 | async-trait = { workspace = true } 19 | derive_more = { workspace = true, features = ["from", "into", "deref", "display"] } 20 | log = { workspace = true } 21 | serde = { workspace = true } 22 | rand = { workspace = true } 23 | tracing-subscriber = { workspace = true, features = ["env-filter", "std"] } 24 | futures = { workspace = true } 25 | parking_lot = { workspace = true } 26 | httparse = { workspace = true } 27 | tls-parser = { workspace = true } 28 | rtsp-types = { workspace = true } 29 | bincode = { workspace = true } 30 | poem = { workspace = true, optional = true } 31 | metrics = { workspace = true } 32 | tokio-yamux = { workspace = true } 33 | tokio-rustls = { workspace = true } 34 | 35 | [dev-dependencies] 36 | test-log = { workspace = true } 37 | 38 | [features] 39 | default = ["binary"] 40 | expose-metrics = ["metrics-dashboard", "poem"] 41 | binary = ["protocol-ed25519", "expose-metrics"] 42 | -------------------------------------------------------------------------------- /bin/relayer/debug.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bin/relayer/run_local_node1.sh: -------------------------------------------------------------------------------- 1 | RUST_LOG=info cargo run -- \ 2 | --proxy-http-listener 127.0.0.1:11001 \ 3 | --proxy-tls-listener 127.0.0.1:12001 \ 4 | --proxy-rtsp-listener 127.0.0.1:5341 \ 5 | --proxy-rtsps-listener 127.0.0.1:35341 \ 6 | --agent-secure-listener 127.0.0.1:13001 \ 7 | --agent-unsecure-listener 127.0.0.1:23001 \ 8 | --root-domain local.ha.8xff.io \ 9 | --sdn-peer-id 1 \ 10 | --sdn-listener 127.0.0.1:14001 \ 11 | --sdn-advertise-address 127.0.0.1:14001 -------------------------------------------------------------------------------- /bin/relayer/run_local_node2.sh: -------------------------------------------------------------------------------- 1 | RUST_LOG=info cargo run -- \ 2 | --proxy-http-listener 127.0.0.1:11002 \ 3 | --proxy-tls-listener 127.0.0.1:12002 \ 4 | --proxy-rtsp-listener 127.0.0.1:5342 \ 5 | --proxy-rtsps-listener 127.0.0.1:35342 \ 6 | --agent-secure-listener 127.0.0.1:13002 \ 7 | --agent-unsecure-listener 127.0.0.1:23002 \ 8 | --root-domain local.ha.8xff.io \ 9 | --sdn-peer-id 2 \ 10 | --sdn-listener 127.0.0.1:14002 \ 11 | --sdn-seeds 1@127.0.0.1:14001 \ -------------------------------------------------------------------------------- /bin/relayer/run_local_node3.sh: -------------------------------------------------------------------------------- 1 | RUST_LOG=info cargo run -- \ 2 | --proxy-http-listener 127.0.0.1:11003 \ 3 | --proxy-tls-listener 127.0.0.1:12003 \ 4 | --proxy-rtsp-listener 127.0.0.1:5343 \ 5 | --proxy-rtsps-listener 127.0.0.1:35343 \ 6 | --agent-secure-listener 127.0.0.1:13003 \ 7 | --agent-unsecure-listener 127.0.0.1:23003 \ 8 | --root-domain local.ha.8xff.io \ 9 | --sdn-peer-id 3 \ 10 | --sdn-listener 127.0.0.1:14003 \ 11 | --sdn-seeds 1@127.0.0.1:14002 \ -------------------------------------------------------------------------------- /bin/relayer/src/agent.rs: -------------------------------------------------------------------------------- 1 | use derive_more::derive::{Deref, Display, From}; 2 | use protocol::proxy::AgentId; 3 | use tokio::{ 4 | io::{AsyncRead, AsyncWrite}, 5 | sync::{mpsc::Sender, oneshot}, 6 | }; 7 | 8 | pub mod quic; 9 | pub mod tcp; 10 | pub mod tls; 11 | 12 | #[derive(Debug, Hash, Display, PartialEq, Eq, From, Deref, Clone, Copy)] 13 | pub struct AgentSessionId(u64); 14 | 15 | impl AgentSessionId { 16 | pub fn rand() -> Self { 17 | Self(rand::random()) 18 | } 19 | } 20 | 21 | enum AgentSessionControl { 22 | CreateStream(oneshot::Sender>), 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct AgentSession { 27 | agent_id: AgentId, 28 | session_id: AgentSessionId, 29 | domain: String, 30 | control_tx: Sender>, 31 | } 32 | 33 | impl AgentSession { 34 | fn new(agent_id: AgentId, session_id: AgentSessionId, domain: String, control_tx: Sender>) -> Self { 35 | Self { 36 | agent_id, 37 | session_id, 38 | domain, 39 | control_tx, 40 | } 41 | } 42 | 43 | pub fn agent_id(&self) -> AgentId { 44 | self.agent_id 45 | } 46 | } 47 | 48 | impl Clone for AgentSession { 49 | fn clone(&self) -> Self { 50 | Self { 51 | agent_id: self.agent_id, 52 | session_id: self.session_id, 53 | domain: self.domain.clone(), 54 | control_tx: self.control_tx.clone(), 55 | } 56 | } 57 | } 58 | 59 | pub enum AgentListenerEvent { 60 | Connected(AgentId, AgentSession), 61 | IncomingStream(AgentId, C, S), 62 | Disconnected(AgentId, AgentSessionId), 63 | } 64 | 65 | pub trait AgentListener { 66 | async fn recv(&mut self) -> anyhow::Result>; 67 | async fn shutdown(&mut self); 68 | } 69 | 70 | impl AgentSession { 71 | pub fn session_id(&self) -> AgentSessionId { 72 | self.session_id 73 | } 74 | 75 | pub fn domain(&self) -> &str { 76 | &self.domain 77 | } 78 | 79 | pub async fn create_stream(&self) -> anyhow::Result { 80 | let (tx, rx) = oneshot::channel(); 81 | self.control_tx.send(AgentSessionControl::CreateStream(tx)).await?; 82 | rx.await? 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /bin/relayer/src/agent/quic.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, net::SocketAddr, sync::Arc, time::Instant}; 2 | 3 | use anyhow::anyhow; 4 | use metrics::histogram; 5 | use protocol::{ 6 | key::{ClusterRequest, ClusterValidator}, 7 | proxy::AgentId, 8 | stream::TunnelStream, 9 | }; 10 | use quinn::{Endpoint, Incoming, VarInt}; 11 | use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 12 | use serde::de::DeserializeOwned; 13 | use tokio::{ 14 | select, 15 | sync::mpsc::{channel, Receiver, Sender}, 16 | }; 17 | 18 | use crate::{ 19 | agent::AgentSessionControl, 20 | quic::{make_server_endpoint, TunnelQuicStream}, 21 | METRICS_AGENT_HISTOGRAM, 22 | }; 23 | 24 | use super::{AgentListener, AgentListenerEvent, AgentSession, AgentSessionId}; 25 | 26 | pub struct AgentQuicListener { 27 | validate: Arc, 28 | endpoint: Endpoint, 29 | internal_tx: Sender>, 30 | internal_rx: Receiver>, 31 | _tmp: PhantomData, 32 | } 33 | 34 | impl AgentQuicListener { 35 | pub async fn new(addr: SocketAddr, priv_key: PrivatePkcs8KeyDer<'static>, cert: CertificateDer<'static>, validate: VALIDATE) -> anyhow::Result { 36 | log::info!("[AgentQuic] starting with addr {addr}"); 37 | let endpoint = make_server_endpoint(addr, priv_key, cert)?; 38 | let (internal_tx, internal_rx) = channel(10); 39 | 40 | Ok(Self { 41 | endpoint, 42 | internal_tx, 43 | internal_rx, 44 | validate: validate.into(), 45 | _tmp: PhantomData, 46 | }) 47 | } 48 | } 49 | 50 | impl, REQ: DeserializeOwned + Send + Sync + 'static + ClusterRequest> AgentListener for AgentQuicListener { 51 | async fn recv(&mut self) -> anyhow::Result> { 52 | loop { 53 | select! { 54 | incoming = self.endpoint.accept() => { 55 | let validate = self.validate.clone(); 56 | let internal_tx = self.internal_tx.clone(); 57 | let incoming = incoming.ok_or(anyhow!("quinn crash"))?; 58 | let remote = incoming.remote_address(); 59 | tokio::spawn(async move { 60 | if let Err(e) = run_connection(validate, incoming, internal_tx).await { 61 | log::error!("[AgentQuic] connection {remote} error {e:?}"); 62 | } 63 | }); 64 | }, 65 | event = self.internal_rx.recv() => break Ok(event.expect("should work")), 66 | } 67 | } 68 | } 69 | 70 | async fn shutdown(&mut self) { 71 | self.endpoint.close(VarInt::from_u32(0), "Shutdown".as_bytes()); 72 | } 73 | } 74 | 75 | async fn run_connection, REQ: ClusterRequest>( 76 | validate: Arc, 77 | incoming: Incoming, 78 | internal_tx: Sender>, 79 | ) -> anyhow::Result<()> { 80 | let started = Instant::now(); 81 | log::info!("[AgentQuic] new connection from {}", incoming.remote_address()); 82 | 83 | let conn = incoming.await?; 84 | let (mut send, mut recv) = conn.accept_bi().await?; 85 | let mut buf = [0u8; 4096]; 86 | let buf_len = recv.read(&mut buf).await?.ok_or(anyhow!("no incoming data"))?; 87 | 88 | log::info!("[AgentQuic] new connection got handhsake data {buf_len} bytes"); 89 | 90 | let req = validate.validate_connect_req(&buf[0..buf_len])?; 91 | let domain = validate.generate_domain(&req)?; 92 | let agent_id = AgentId::try_from_domain(&domain)?; 93 | let session_id = AgentSessionId::rand(); 94 | let agent_ctx = req.context(); 95 | 96 | log::info!("[AgentQuic] new connection validated with domain {domain} agent_id: {agent_id}, session uuid: {session_id}"); 97 | 98 | let res_buf = validate.sign_response_res(&req, None); 99 | send.write_all(&res_buf).await?; 100 | let (control_tx, mut control_rx) = channel(10); 101 | 102 | internal_tx 103 | .send(AgentListenerEvent::Connected(agent_id, AgentSession::new(agent_id, session_id, domain, control_tx))) 104 | .await 105 | .expect("should send to main loop"); 106 | 107 | log::info!("[AgentQuic] new connection {agent_id} {session_id} started loop"); 108 | histogram!(METRICS_AGENT_HISTOGRAM).record(started.elapsed().as_millis() as f32 / 1000.0); 109 | 110 | loop { 111 | select! { 112 | control = control_rx.recv() => match control { 113 | Some(control) => match control { 114 | AgentSessionControl::CreateStream(tx) => { 115 | log::info!("[AgentQuic] agent {agent_id} {session_id} create stream request"); 116 | let conn = conn.clone(); 117 | tokio::spawn(async move { 118 | match conn.open_bi().await { 119 | Ok((send, recv)) => { 120 | log::info!("[AgentQuic] agent {agent_id} {session_id} created stream"); 121 | if let Err(_e) = tx.send(Ok(TunnelStream::new(recv, send))) { 122 | log::error!("[AgentQuic] agent {agent_id} {session_id} send created stream error"); 123 | } 124 | }, 125 | Err(err) => { 126 | if let Err(_e) = tx.send(Err(err.into())) { 127 | log::error!("[AgentQuic] agent {agent_id} {session_id} send create stream's error, may be internal channel failed"); 128 | } 129 | }, 130 | } 131 | }); 132 | }, 133 | }, 134 | None => { 135 | break; 136 | } 137 | }, 138 | accept = conn.accept_bi() => match accept { 139 | Ok((send, recv)) => { 140 | let stream = TunnelStream::new(recv, send); 141 | let internal_tx = internal_tx.clone(); 142 | let agent_ctx = agent_ctx.clone(); 143 | tokio::spawn(async move { 144 | internal_tx.send(AgentListenerEvent::IncomingStream(agent_id, agent_ctx, stream)).await.expect("should send to main loop"); 145 | }); 146 | }, 147 | Err(err) => { 148 | log::error!("[AgentQuic] agent {agent_id} {session_id} quic connection error {err:?}"); 149 | break; 150 | }, 151 | } 152 | } 153 | } 154 | 155 | log::info!("[AgentQuic] agent {agent_id} {session_id} stopped loop"); 156 | 157 | internal_tx.send(AgentListenerEvent::Disconnected(agent_id, session_id)).await.expect("should send to main loop"); 158 | 159 | Ok(()) 160 | } 161 | -------------------------------------------------------------------------------- /bin/relayer/src/agent/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, net::SocketAddr, sync::Arc, time::Instant}; 2 | 3 | use futures::StreamExt; 4 | use metrics::histogram; 5 | use protocol::{ 6 | key::{ClusterRequest, ClusterValidator}, 7 | proxy::AgentId, 8 | }; 9 | use serde::de::DeserializeOwned; 10 | use tokio::{ 11 | io::{AsyncReadExt, AsyncWriteExt}, 12 | net::{TcpListener, TcpStream}, 13 | select, 14 | sync::mpsc::{channel, Receiver, Sender}, 15 | }; 16 | 17 | use crate::{agent::AgentSessionControl, METRICS_AGENT_HISTOGRAM}; 18 | use tokio_yamux::{Session, StreamHandle}; 19 | 20 | use super::{AgentListener, AgentListenerEvent, AgentSession, AgentSessionId}; 21 | 22 | pub type TunnelTcpStream = StreamHandle; 23 | 24 | pub struct AgentTcpListener { 25 | validate: Arc, 26 | listener: TcpListener, 27 | internal_tx: Sender>, 28 | internal_rx: Receiver>, 29 | _tmp: PhantomData, 30 | } 31 | 32 | impl AgentTcpListener { 33 | pub async fn new(addr: SocketAddr, validate: VALIDATE) -> anyhow::Result { 34 | log::info!("[AgentTcp] starting with addr {addr}"); 35 | let (internal_tx, internal_rx) = channel(10); 36 | 37 | Ok(Self { 38 | listener: TcpListener::bind(addr).await?, 39 | internal_tx, 40 | internal_rx, 41 | validate: validate.into(), 42 | _tmp: PhantomData, 43 | }) 44 | } 45 | } 46 | 47 | impl, REQ: DeserializeOwned + Send + Sync + 'static + ClusterRequest> AgentListener for AgentTcpListener { 48 | async fn recv(&mut self) -> anyhow::Result> { 49 | loop { 50 | select! { 51 | incoming = self.listener.accept() => { 52 | let (stream, remote) = incoming?; 53 | let validate = self.validate.clone(); 54 | let internal_tx = self.internal_tx.clone(); 55 | tokio::spawn(async move { 56 | if let Err(e) = run_connection(validate, stream, remote, internal_tx).await { 57 | log::error!("[AgentTcp] connection {remote} error {e:?}"); 58 | } 59 | }); 60 | }, 61 | event = self.internal_rx.recv() => break Ok(event.expect("should receive event from internal channel")), 62 | } 63 | } 64 | } 65 | 66 | async fn shutdown(&mut self) {} 67 | } 68 | 69 | async fn run_connection, REQ: ClusterRequest>( 70 | validate: Arc, 71 | mut in_stream: TcpStream, 72 | remote: SocketAddr, 73 | internal_tx: Sender>, 74 | ) -> anyhow::Result<()> { 75 | let started = Instant::now(); 76 | log::info!("[AgentTcp] new connection from {}", remote); 77 | 78 | let mut buf = [0u8; 4096]; 79 | let buf_len = in_stream.read(&mut buf).await?; 80 | 81 | log::info!("[AgentTcp] new connection got handhsake data {buf_len} bytes"); 82 | 83 | let req = validate.validate_connect_req(&buf[0..buf_len])?; 84 | let domain = validate.generate_domain(&req)?; 85 | let agent_id = AgentId::try_from_domain(&domain)?; 86 | let session_id = AgentSessionId::rand(); 87 | let agent_ctx = req.context(); 88 | 89 | log::info!("[AgentTcp] new connection validated with domain {domain} agent_id: {agent_id}, session uuid: {session_id}"); 90 | 91 | let res_buf = validate.sign_response_res(&req, None); 92 | in_stream.write_all(&res_buf).await?; 93 | let (control_tx, mut control_rx) = channel(10); 94 | 95 | internal_tx 96 | .send(AgentListenerEvent::Connected(agent_id, AgentSession::new(agent_id, session_id, domain, control_tx))) 97 | .await 98 | .expect("should send to main loop"); 99 | 100 | log::info!("[AgentTcp] new connection {agent_id} {session_id} started loop"); 101 | let mut session = Session::new_client(in_stream, Default::default()); 102 | histogram!(METRICS_AGENT_HISTOGRAM).record(started.elapsed().as_millis() as f32 / 1000.0); 103 | 104 | loop { 105 | select! { 106 | control = control_rx.recv() => match control { 107 | Some(control) => match control { 108 | AgentSessionControl::CreateStream(tx) => { 109 | log::info!("[AgentTcp] agent {agent_id} {session_id} create stream request"); 110 | match session.open_stream() { 111 | Ok(stream) => { 112 | log::info!("[AgentTcp] agent {agent_id} {session_id} created stream"); 113 | if let Err(_e) = tx.send(Ok(stream)) { 114 | log::error!("[AgentTcp] agent {agent_id} {session_id} send created stream error"); 115 | } 116 | }, 117 | Err(err) => { 118 | if let Err(_e) = tx.send(Err(err.into())) { 119 | log::error!("[AgentTcp] agent {agent_id} {session_id} send create stream's error, may be internal channel failed"); 120 | } 121 | }, 122 | } 123 | }, 124 | }, 125 | None => { 126 | break; 127 | } 128 | }, 129 | accept = session.next() => match accept { 130 | Some(Ok(stream)) => { 131 | let internal_tx = internal_tx.clone(); 132 | let agent_ctx = agent_ctx.clone(); 133 | tokio::spawn(async move { 134 | internal_tx.send(AgentListenerEvent::IncomingStream(agent_id, agent_ctx, stream)).await.expect("should send to main loop"); 135 | }); 136 | }, 137 | Some(Err(err)) => { 138 | log::error!("[AgentTcp] agent {agent_id} {session_id} Tcp connection error {err:?}"); 139 | break; 140 | }, 141 | None => { 142 | log::error!("[AgentTcp] agent {agent_id} {session_id} Tcp connection broken with None"); 143 | break; 144 | } 145 | } 146 | } 147 | } 148 | 149 | log::info!("[AgentTcp] agent {agent_id} {session_id} stopped loop"); 150 | 151 | internal_tx.send(AgentListenerEvent::Disconnected(agent_id, session_id)).await.expect("should send to main loop"); 152 | 153 | Ok(()) 154 | } 155 | -------------------------------------------------------------------------------- /bin/relayer/src/agent/tls.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, net::SocketAddr, sync::Arc, time::Instant}; 2 | 3 | use futures::StreamExt; 4 | use metrics::histogram; 5 | use protocol::key::{ClusterRequest, ClusterValidator}; 6 | use protocol::proxy::AgentId; 7 | use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; 8 | use serde::de::DeserializeOwned; 9 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 10 | use tokio::net::TcpStream; 11 | use tokio::{ 12 | net::TcpListener, 13 | select, 14 | sync::mpsc::{channel, Receiver, Sender}, 15 | }; 16 | use tokio_rustls::TlsAcceptor; 17 | use tokio_yamux::{Session, StreamHandle}; 18 | 19 | use crate::agent::{AgentSession, AgentSessionControl}; 20 | use crate::{AgentSessionId, METRICS_AGENT_HISTOGRAM}; 21 | 22 | use super::{AgentListener, AgentListenerEvent}; 23 | 24 | pub type TunnelTlsStream = StreamHandle; 25 | pub struct AgentTlsListener { 26 | tls_acceptor: Arc, 27 | validate: Arc, 28 | listener: TcpListener, 29 | internal_tx: Sender>, 30 | internal_rx: Receiver>, 31 | _tmp: PhantomData, 32 | } 33 | 34 | impl AgentTlsListener { 35 | pub async fn new(addr: SocketAddr, validate: VALIDATE, key: PrivatePkcs8KeyDer<'static>, cert: CertificateDer<'static>) -> anyhow::Result { 36 | log::info!("[AgentTls] starting with addr {addr}"); 37 | let (internal_tx, internal_rx) = channel(10); 38 | let config = rustls::ServerConfig::builder().with_no_client_auth().with_single_cert(vec![cert], PrivateKeyDer::Pkcs8(key))?; 39 | let tls_acceptor = TlsAcceptor::from(Arc::new(config)); 40 | 41 | Ok(Self { 42 | tls_acceptor: Arc::new(tls_acceptor), 43 | listener: TcpListener::bind(addr).await?, 44 | internal_tx, 45 | internal_rx, 46 | validate: validate.into(), 47 | _tmp: PhantomData, 48 | }) 49 | } 50 | } 51 | 52 | impl, REQ: DeserializeOwned + Send + Sync + 'static + ClusterRequest> AgentListener for AgentTlsListener { 53 | async fn recv(&mut self) -> anyhow::Result> { 54 | loop { 55 | let (stream, remote) = select! { 56 | incoming = self.listener.accept() => incoming?, 57 | event = self.internal_rx.recv() => break Ok(event.expect("should receive event from internal channel")), 58 | }; 59 | 60 | let tls_acceptor = self.tls_acceptor.clone(); 61 | let validate = self.validate.clone(); 62 | let internal_tx = self.internal_tx.clone(); 63 | tokio::spawn(async move { 64 | if let Err(e) = run_connection(validate, tls_acceptor, stream, remote, internal_tx).await { 65 | log::error!("[AgentTls] connection {remote} error {e:?}"); 66 | } 67 | }); 68 | } 69 | } 70 | 71 | async fn shutdown(&mut self) {} 72 | } 73 | 74 | async fn run_connection, REQ: ClusterRequest>( 75 | validate: Arc, 76 | tls_acceptor: Arc, 77 | stream: TcpStream, 78 | remote: SocketAddr, 79 | internal_tx: Sender>, 80 | ) -> anyhow::Result<()> { 81 | let started = Instant::now(); 82 | log::info!("[AgentTls] new connection from {remote}, handshaking tls"); 83 | let mut in_stream = tls_acceptor.accept(stream).await?; 84 | log::info!("[AgentTls] new connection from {remote}, handshake tls success"); 85 | 86 | let mut buf = [0u8; 4096]; 87 | let buf_len = in_stream.read(&mut buf).await?; 88 | 89 | log::info!("[AgentTls] new connection from {remote} got handhsake data {buf_len} bytes"); 90 | 91 | let req = validate.validate_connect_req(&buf[0..buf_len])?; 92 | let domain = validate.generate_domain(&req)?; 93 | let agent_id = AgentId::try_from_domain(&domain)?; 94 | let session_id = AgentSessionId::rand(); 95 | let agent_ctx = req.context(); 96 | 97 | log::info!("[AgentTls] new connection from {remote} validated with domain {domain} agent_id: {agent_id}, session uuid: {session_id}"); 98 | 99 | let res_buf = validate.sign_response_res(&req, None); 100 | in_stream.write_all(&res_buf).await?; 101 | let (control_tx, mut control_rx) = channel(10); 102 | 103 | internal_tx 104 | .send(AgentListenerEvent::Connected(agent_id, AgentSession::new(agent_id, session_id, domain, control_tx))) 105 | .await 106 | .expect("should send to main loop"); 107 | 108 | log::info!("[AgentTls] new connection {agent_id} {session_id} started loop"); 109 | let mut session = Session::new_client(in_stream, Default::default()); 110 | histogram!(METRICS_AGENT_HISTOGRAM).record(started.elapsed().as_millis() as f32 / 1000.0); 111 | 112 | loop { 113 | select! { 114 | control = control_rx.recv() => match control { 115 | Some(control) => match control { 116 | AgentSessionControl::CreateStream(tx) => { 117 | log::info!("[AgentTls] agent {agent_id} {session_id} create stream request"); 118 | match session.open_stream() { 119 | Ok(stream) => { 120 | log::info!("[AgentTls] agent {agent_id} {session_id} created stream"); 121 | if let Err(_e) = tx.send(Ok(stream)) { 122 | log::error!("[AgentTls] agent {agent_id} {session_id} send created stream error"); 123 | } 124 | }, 125 | Err(err) => { 126 | if let Err(_e) = tx.send(Err(err.into())) { 127 | log::error!("[AgentTls] agent {agent_id} {session_id} send create stream's error, may be internal channel failed"); 128 | } 129 | }, 130 | } 131 | }, 132 | }, 133 | None => { 134 | break; 135 | } 136 | }, 137 | accept = session.next() => match accept { 138 | Some(Ok(stream)) => { 139 | let internal_tx = internal_tx.clone(); 140 | let agent_ctx = agent_ctx.clone(); 141 | tokio::spawn(async move { 142 | internal_tx.send(AgentListenerEvent::IncomingStream(agent_id, agent_ctx, stream)).await.expect("should send to main loop"); 143 | }); 144 | }, 145 | Some(Err(err)) => { 146 | log::error!("[AgentTls] agent {agent_id} {session_id} Tcp connection error {err:?}"); 147 | break; 148 | }, 149 | None => { 150 | log::error!("[AgentTls] agent {agent_id} {session_id} Tcp connection broken with None"); 151 | break; 152 | } 153 | } 154 | } 155 | } 156 | 157 | log::info!("[AgentTls] agent {agent_id} {session_id} stopped loop"); 158 | 159 | internal_tx.send(AgentListenerEvent::Disconnected(agent_id, session_id)).await.expect("should send to main loop"); 160 | 161 | Ok(()) 162 | } 163 | -------------------------------------------------------------------------------- /bin/relayer/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, net::SocketAddr, time::Instant}; 2 | 3 | use ::metrics::{counter, gauge, histogram}; 4 | use agent::{ 5 | quic::AgentQuicListener, 6 | tcp::{AgentTcpListener, TunnelTcpStream}, 7 | tls::{AgentTlsListener, TunnelTlsStream}, 8 | AgentListener, AgentListenerEvent, AgentSession, 9 | }; 10 | use anyhow::anyhow; 11 | use p2p::{ 12 | alias_service::{AliasGuard, AliasService, AliasServiceRequester}, 13 | HandshakeProtocol, P2pNetwork, P2pNetworkConfig, P2pService, P2pServiceEvent, P2pServiceRequester, PeerAddress, PeerId, 14 | }; 15 | use protocol::{ 16 | cluster::{write_object, AgentTunnelRequest}, 17 | key::{ClusterRequest, ClusterValidator}, 18 | proxy::{AgentId, ProxyDestination}, 19 | }; 20 | use quic::TunnelQuicStream; 21 | use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 22 | use serde::de::DeserializeOwned; 23 | use tokio::{ 24 | io::{copy_bidirectional, AsyncRead, AsyncWrite}, 25 | select, 26 | }; 27 | 28 | mod agent; 29 | mod metrics; 30 | mod proxy; 31 | mod quic; 32 | 33 | pub use agent::AgentSessionId; 34 | pub use metrics::*; 35 | pub use p2p; 36 | pub use proxy::{http::HttpDestinationDetector, rtsp::RtspDestinationDetector, tls::TlsDestinationDetector, ProxyDestinationDetector, ProxyTcpListener}; 37 | 38 | const ALIAS_SERVICE: u16 = 0; 39 | const PROXY_TO_AGENT_SERVICE: u16 = 1; 40 | const TUNNEL_TO_CLUSTER_SERVICE: u16 = 2; 41 | 42 | #[derive(Clone)] 43 | pub struct TunnelServiceCtx { 44 | pub service: P2pServiceRequester, 45 | pub alias: AliasServiceRequester, 46 | } 47 | 48 | /// This service take care how we process a incoming request from agent 49 | pub trait TunnelServiceHandle { 50 | fn start(&mut self, _ctx: &TunnelServiceCtx); 51 | fn on_agent_conn(&mut self, _ctx: &TunnelServiceCtx, _agent_id: AgentId, ctx: Ctx, _stream: S); 52 | fn on_cluster_event(&mut self, _ctx: &TunnelServiceCtx, _event: P2pServiceEvent); 53 | } 54 | 55 | pub struct QuicRelayerConfig { 56 | pub agent_unsecure_listener: SocketAddr, 57 | pub agent_secure_listener: SocketAddr, 58 | pub proxy_http_listener: SocketAddr, 59 | pub proxy_tls_listener: SocketAddr, 60 | pub proxy_rtsp_listener: SocketAddr, 61 | pub proxy_rtsps_listener: SocketAddr, 62 | 63 | pub agent_key: PrivatePkcs8KeyDer<'static>, 64 | pub agent_cert: CertificateDer<'static>, 65 | 66 | pub sdn_peer_id: PeerId, 67 | pub sdn_listener: SocketAddr, 68 | pub sdn_seeds: Vec, 69 | pub sdn_key: PrivatePkcs8KeyDer<'static>, 70 | pub sdn_cert: CertificateDer<'static>, 71 | pub sdn_advertise_address: Option, 72 | pub sdn_secure: SECURE, 73 | 74 | pub tunnel_service_handle: TSH, 75 | } 76 | 77 | pub enum QuicRelayerEvent { 78 | AgentConnected(AgentId, AgentSessionId, String), 79 | AgentDisconnected(AgentId, AgentSessionId), 80 | Continue, 81 | } 82 | 83 | pub struct QuicRelayer { 84 | agent_quic: AgentQuicListener, 85 | agent_tcp: AgentTcpListener, 86 | agent_tls: AgentTlsListener, 87 | http_proxy: ProxyTcpListener, 88 | tls_proxy: ProxyTcpListener, 89 | rtsp_proxy: ProxyTcpListener, 90 | rtsps_proxy: ProxyTcpListener, 91 | 92 | sdn: P2pNetwork, 93 | 94 | sdn_alias_requester: AliasServiceRequester, 95 | // This service is for proxy from internet to agent 96 | sdn_proxy_service: P2pService, 97 | // This service is for tunnel from agent to outside 98 | sdn_tunnel_service: P2pService, 99 | tunnel_service_ctx: TunnelServiceCtx, 100 | tunnel_service_handle: TSH, 101 | 102 | agent_quic_sessions: HashMap, AliasGuard)>>, 103 | agent_tcp_sessions: HashMap, AliasGuard)>>, 104 | agent_tls_sessions: HashMap, AliasGuard)>>, 105 | } 106 | 107 | impl QuicRelayer 108 | where 109 | SECURE: HandshakeProtocol, 110 | VALIDATE: ClusterValidator, 111 | REQ: DeserializeOwned + Send + Sync + 'static, 112 | TSH: TunnelServiceHandle + Send + Sync + 'static, 113 | { 114 | pub async fn new(mut cfg: QuicRelayerConfig, validate: VALIDATE) -> anyhow::Result { 115 | let mut sdn = P2pNetwork::new(P2pNetworkConfig { 116 | peer_id: cfg.sdn_peer_id, 117 | listen_addr: cfg.sdn_listener, 118 | advertise: cfg.sdn_advertise_address.map(|a| a.into()), 119 | priv_key: cfg.sdn_key, 120 | cert: cfg.sdn_cert, 121 | tick_ms: 1000, 122 | seeds: cfg.sdn_seeds, 123 | secure: cfg.sdn_secure, 124 | }) 125 | .await?; 126 | 127 | let mut sdn_alias = AliasService::new(sdn.create_service(ALIAS_SERVICE.into())); 128 | let sdn_alias_requester = sdn_alias.requester(); 129 | tokio::spawn(async move { while sdn_alias.run_loop().await.is_ok() {} }); 130 | let sdn_proxy_service = sdn.create_service(PROXY_TO_AGENT_SERVICE.into()); 131 | let sdn_tunnel_service = sdn.create_service(TUNNEL_TO_CLUSTER_SERVICE.into()); 132 | let tunnel_service_ctx = TunnelServiceCtx { 133 | service: sdn_tunnel_service.requester(), 134 | alias: sdn_alias_requester.clone(), 135 | }; 136 | cfg.tunnel_service_handle.start(&tunnel_service_ctx); 137 | 138 | Ok(Self { 139 | agent_quic: AgentQuicListener::new(cfg.agent_secure_listener, cfg.agent_key.clone_key(), cfg.agent_cert.clone(), validate.clone()).await?, 140 | agent_tcp: AgentTcpListener::new(cfg.agent_unsecure_listener, validate.clone()).await?, 141 | agent_tls: AgentTlsListener::new(cfg.agent_secure_listener, validate, cfg.agent_key, cfg.agent_cert).await?, 142 | http_proxy: ProxyTcpListener::new(cfg.proxy_http_listener, Default::default()).await?, 143 | tls_proxy: ProxyTcpListener::new(cfg.proxy_tls_listener, Default::default()).await?, 144 | rtsp_proxy: ProxyTcpListener::new(cfg.proxy_rtsp_listener, Default::default()).await?, 145 | rtsps_proxy: ProxyTcpListener::new(cfg.proxy_rtsps_listener, Default::default()).await?, 146 | 147 | sdn, 148 | sdn_alias_requester, 149 | sdn_proxy_service, 150 | sdn_tunnel_service, 151 | tunnel_service_handle: cfg.tunnel_service_handle, 152 | tunnel_service_ctx, 153 | 154 | agent_quic_sessions: HashMap::new(), 155 | agent_tcp_sessions: HashMap::new(), 156 | agent_tls_sessions: HashMap::new(), 157 | }) 158 | } 159 | 160 | fn process_proxy(&mut self, proxy: T, dest: ProxyDestination, is_from_cluster: bool) { 161 | let agent_id = match dest.agent_id() { 162 | Ok(agent_id) => agent_id, 163 | Err(e) => { 164 | log::warn!("[QuicRelayer] proxy to {dest:?} failed to get agent id: {e}"); 165 | return; 166 | } 167 | }; 168 | if let Some(sessions) = self.agent_tcp_sessions.get(&agent_id) { 169 | let session = sessions.values().next().expect("should have session"); 170 | let job = proxy_local_to_agent(is_from_cluster, proxy, dest, session.0.clone()); 171 | tokio::spawn(async move { 172 | if let Err(e) = job.await { 173 | counter!(METRICS_PROXY_HTTP_ERROR_COUNT).increment(1); 174 | counter!(METRICS_TUNNEL_AGENT_ERROR_COUNT).increment(1); 175 | log::error!("[QuicRelayer {agent_id}] proxy to agent error {:?}", e); 176 | }; 177 | }); 178 | } else if let Some(sessions) = self.agent_tls_sessions.get(&agent_id) { 179 | let session = sessions.values().next().expect("should have session"); 180 | let job = proxy_local_to_agent(is_from_cluster, proxy, dest, session.0.clone()); 181 | tokio::spawn(async move { 182 | if let Err(e) = job.await { 183 | counter!(METRICS_PROXY_HTTP_ERROR_COUNT).increment(1); 184 | counter!(METRICS_TUNNEL_AGENT_ERROR_COUNT).increment(1); 185 | log::error!("[QuicRelayer {agent_id}] proxy to agent error {:?}", e); 186 | }; 187 | }); 188 | } else if let Some(sessions) = self.agent_quic_sessions.get(&agent_id) { 189 | let session = sessions.values().next().expect("should have session"); 190 | let job = proxy_local_to_agent(is_from_cluster, proxy, dest, session.0.clone()); 191 | tokio::spawn(async move { 192 | if let Err(e) = job.await { 193 | counter!(METRICS_PROXY_HTTP_ERROR_COUNT).increment(1); 194 | counter!(METRICS_TUNNEL_AGENT_ERROR_COUNT).increment(1); 195 | log::error!("[QuicRelayer {agent_id}] proxy to agent error {:?}", e); 196 | }; 197 | }); 198 | } else if !is_from_cluster { 199 | // we don't allow two times tunnel over cluster 200 | let sdn_requester = self.sdn_proxy_service.requester(); 201 | let job = proxy_to_cluster(proxy, dest, self.sdn_alias_requester.clone(), sdn_requester); 202 | tokio::spawn(async move { 203 | if let Err(e) = job.await { 204 | counter!(METRICS_PROXY_HTTP_ERROR_COUNT).increment(1); 205 | counter!(METRICS_TUNNEL_CLUSTER_ERROR_COUNT).increment(1); 206 | log::error!("[QuicRelayer {agent_id}] proxy to cluster error {:?}", e); 207 | }; 208 | }); 209 | } else { 210 | log::warn!("[QuicRelayer {agent_id}] proxy to {dest:?} not match any kind"); 211 | counter!(METRICS_PROXY_CLUSTER_ERROR_COUNT).increment(1); 212 | counter!(METRICS_TUNNEL_AGENT_ERROR_COUNT).increment(1); 213 | } 214 | } 215 | 216 | pub fn p2p(&mut self) -> &mut P2pNetwork { 217 | &mut self.sdn 218 | } 219 | 220 | pub async fn recv(&mut self) -> anyhow::Result { 221 | select! { 222 | tunnel = self.http_proxy.recv() => { 223 | let (dest, tunnel) = tunnel?; 224 | self.process_proxy(tunnel, dest, false); 225 | Ok(QuicRelayerEvent::Continue) 226 | }, 227 | tunnel = self.tls_proxy.recv() => { 228 | let (dest, tunnel) = tunnel?; 229 | self.process_proxy(tunnel, dest, false); 230 | Ok(QuicRelayerEvent::Continue) 231 | }, 232 | tunnel = self.rtsp_proxy.recv() => { 233 | let (dest, tunnel) = tunnel?; 234 | self.process_proxy(tunnel, dest, false); 235 | Ok(QuicRelayerEvent::Continue) 236 | }, 237 | tunnel = self.rtsps_proxy.recv() => { 238 | let (dest, tunnel) = tunnel?; 239 | self.process_proxy(tunnel, dest, false); 240 | Ok(QuicRelayerEvent::Continue) 241 | }, 242 | _ = self.sdn.recv() => { 243 | Ok(QuicRelayerEvent::Continue) 244 | }, 245 | event = self.agent_quic.recv() => process_incoming_event::<_, _, REQ>(event?, &self.sdn_alias_requester, &mut self.agent_quic_sessions, &mut self.tunnel_service_handle, &self.tunnel_service_ctx), 246 | event = self.agent_tcp.recv() => process_incoming_event::<_, _, REQ>(event?, &self.sdn_alias_requester, &mut self.agent_tcp_sessions, &mut self.tunnel_service_handle, &self.tunnel_service_ctx), 247 | event = self.agent_tls.recv() => process_incoming_event::<_, _, REQ>(event?, &self.sdn_alias_requester, &mut self.agent_tls_sessions, &mut self.tunnel_service_handle, &self.tunnel_service_ctx), 248 | event = self.sdn_proxy_service.recv() => match event.expect("sdn channel crash") { 249 | P2pServiceEvent::Unicast(from, ..) => { 250 | log::warn!("[QuicRelayer] proxy service don't accept unicast msg from {from}"); 251 | Ok(QuicRelayerEvent::Continue) 252 | }, 253 | P2pServiceEvent::Broadcast(from, ..) => { 254 | log::warn!("[QuicRelayer] proxy service don't accept broadcast msg from {from}"); 255 | Ok(QuicRelayerEvent::Continue) 256 | }, 257 | P2pServiceEvent::Stream(_from, meta, stream) => { 258 | if let Ok(proxy_dest) = bincode::deserialize::(&meta) { 259 | self.process_proxy(stream, proxy_dest, true); 260 | } 261 | Ok(QuicRelayerEvent::Continue) 262 | }, 263 | }, 264 | event = self.sdn_tunnel_service.recv() => { 265 | self.tunnel_service_handle.on_cluster_event(&self.tunnel_service_ctx, event.expect("sdn channel crash")); 266 | Ok(QuicRelayerEvent::Continue) 267 | }, 268 | _ = tokio::signal::ctrl_c() => { 269 | log::info!("[QuicRelayer] shutdown inprogress"); 270 | self.sdn.shutdown(); 271 | self.agent_quic.shutdown().await; 272 | 273 | log::info!("[QuicRelayer] shutdown done"); 274 | Ok(QuicRelayerEvent::Continue) 275 | } 276 | } 277 | } 278 | } 279 | 280 | fn process_incoming_event( 281 | event: AgentListenerEvent, 282 | alias_requester: &AliasServiceRequester, 283 | sessions: &mut HashMap, AliasGuard)>>, 284 | tunnel_service_handle: &mut TSH, 285 | tunnel_service_ctx: &TunnelServiceCtx, 286 | ) -> anyhow::Result 287 | where 288 | S: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static, 289 | REQ: ClusterRequest, 290 | TSH: TunnelServiceHandle + Send + Sync + 'static, 291 | { 292 | match event { 293 | AgentListenerEvent::Connected(agent_id, agent_session) => { 294 | counter!(METRICS_AGENT_COUNT).increment(1); 295 | log::info!("[QuicRelayer] agent {agent_id} {} connected", agent_session.session_id()); 296 | let session_id = agent_session.session_id(); 297 | let domain = agent_session.domain().to_owned(); 298 | let alias = alias_requester.register(*agent_id); 299 | sessions.entry(agent_id).or_default().insert(agent_session.session_id(), (agent_session, alias)); 300 | gauge!(METRICS_AGENT_LIVE).increment(1.0); 301 | Ok(QuicRelayerEvent::AgentConnected(agent_id, session_id, domain)) 302 | } 303 | AgentListenerEvent::IncomingStream(agent_id, agent_ctx, stream) => { 304 | tunnel_service_handle.on_agent_conn(tunnel_service_ctx, agent_id, agent_ctx, stream); 305 | Ok(QuicRelayerEvent::Continue) 306 | } 307 | AgentListenerEvent::Disconnected(agent_id, session_id) => { 308 | log::info!("[QuicRelayer] agent {agent_id} {session_id} disconnected"); 309 | if let Some(child_sessions) = sessions.get_mut(&agent_id) { 310 | child_sessions.remove(&session_id); 311 | if child_sessions.is_empty() { 312 | log::info!("[QuicRelayer] agent disconnected all connections {agent_id} {session_id}"); 313 | sessions.remove(&agent_id); 314 | } 315 | gauge!(METRICS_AGENT_LIVE).decrement(1.0); 316 | } 317 | Ok(QuicRelayerEvent::AgentDisconnected(agent_id, session_id)) 318 | } 319 | } 320 | } 321 | 322 | async fn proxy_local_to_agent( 323 | is_from_cluster: bool, 324 | mut proxy: T, 325 | dest: ProxyDestination, 326 | agent: AgentSession, 327 | ) -> anyhow::Result<()> { 328 | let agent_id = agent.agent_id(); 329 | let started = Instant::now(); 330 | if is_from_cluster { 331 | counter!(METRICS_PROXY_CLUSTER_COUNT).increment(1); 332 | } else { 333 | counter!(METRICS_PROXY_HTTP_COUNT).increment(1); 334 | } 335 | counter!(METRICS_TUNNEL_AGENT_COUNT).increment(1); 336 | log::info!("[ProxyLocal {agent_id}] creating stream to agent"); 337 | let mut stream = agent.create_stream().await?; 338 | 339 | histogram!(METRICS_TUNNEL_AGENT_HISTOGRAM).record(started.elapsed().as_millis() as f32 / 1000.0); 340 | log::info!("[ProxyLocal {agent_id}] created stream to agent => writing connect request"); 341 | write_object::<_, _, 500>( 342 | &mut stream, 343 | &AgentTunnelRequest { 344 | service: dest.service, 345 | tls: dest.tls, 346 | domain: dest.domain, 347 | }, 348 | ) 349 | .await?; 350 | 351 | log::info!("[ProxyLocal {agent_id}] proxy data with agent ..."); 352 | 353 | gauge!(METRICS_TUNNEL_AGENT_LIVE).increment(1.0); 354 | if is_from_cluster { 355 | gauge!(METRICS_PROXY_CLUSTER_LIVE).increment(1.0); 356 | } else { 357 | gauge!(METRICS_PROXY_HTTP_LIVE).increment(1.0); 358 | } 359 | match copy_bidirectional(&mut proxy, &mut stream).await { 360 | Ok(res) => { 361 | log::info!("[ProxyLocal {agent_id}] proxy data with agent done with res {res:?}"); 362 | } 363 | Err(e) => { 364 | log::error!("[ProxyLocal {agent_id}] proxy data with agent error {e}"); 365 | } 366 | }; 367 | 368 | if is_from_cluster { 369 | gauge!(METRICS_PROXY_CLUSTER_LIVE).decrement(1.0); 370 | } else { 371 | gauge!(METRICS_PROXY_HTTP_LIVE).decrement(1.0); 372 | } 373 | gauge!(METRICS_TUNNEL_AGENT_LIVE).decrement(1.0); 374 | 375 | Ok(()) 376 | } 377 | 378 | async fn proxy_to_cluster( 379 | mut proxy: T, 380 | dest: ProxyDestination, 381 | alias_requeser: AliasServiceRequester, 382 | sdn_requester: P2pServiceRequester, 383 | ) -> anyhow::Result<()> { 384 | let started = Instant::now(); 385 | counter!(METRICS_PROXY_HTTP_COUNT).increment(1); 386 | counter!(METRICS_TUNNEL_CLUSTER_COUNT).increment(1); 387 | let agent_id = dest.agent_id()?; 388 | log::info!("[ProxyCluster {agent_id}] finding location of agent {agent_id}"); 389 | let found_location = alias_requeser.find(*agent_id).await.ok_or(anyhow!("ALIAS_NOT_FOUND"))?; 390 | let dest_node = match found_location { 391 | p2p::alias_service::AliasFoundLocation::Local => return Err(anyhow!("wrong alias context, cluster shouldn't in local")), 392 | p2p::alias_service::AliasFoundLocation::Hint(dest) => dest, 393 | p2p::alias_service::AliasFoundLocation::Scan(dest) => dest, 394 | }; 395 | log::info!("[ProxyCluster {agent_id}] found location of agent {agent_id}: {found_location:?} => opening cluster connection to {dest_node}"); 396 | 397 | let meta = bincode::serialize(&dest).expect("should convert ProxyDestination to bytes"); 398 | 399 | let mut stream = sdn_requester.open_stream(dest_node, meta).await?; 400 | histogram!(METRICS_TUNNEL_CLUSTER_HISTOGRAM).record(started.elapsed().as_millis() as f32 / 1000.0); 401 | 402 | log::info!("[ProxyCluster {agent_id}] proxy over {dest_node} ..."); 403 | gauge!(METRICS_TUNNEL_CLUSTER_LIVE).increment(1.0); 404 | gauge!(METRICS_PROXY_HTTP_LIVE).increment(1.0); 405 | 406 | match copy_bidirectional(&mut proxy, &mut stream).await { 407 | Ok(res) => { 408 | log::info!("[ProxyCluster {agent_id}] proxy over {dest_node} done with res {res:?}"); 409 | } 410 | Err(e) => { 411 | log::error!("[ProxyCluster {agent_id}] proxy over {dest_node} error {e}"); 412 | } 413 | } 414 | 415 | gauge!(METRICS_PROXY_HTTP_LIVE).decrement(1.0); 416 | gauge!(METRICS_TUNNEL_CLUSTER_LIVE).decrement(1.0); 417 | Ok(()) 418 | } 419 | -------------------------------------------------------------------------------- /bin/relayer/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use atm0s_reverse_proxy_relayer::{QuicRelayer, QuicRelayerConfig, TunnelServiceHandle}; 4 | use clap::Parser; 5 | use p2p::SharedKeyHandshake; 6 | use protocol::{DEFAULT_CLUSTER_CERT, DEFAULT_CLUSTER_KEY, DEFAULT_TUNNEL_CERT, DEFAULT_TUNNEL_KEY}; 7 | use protocol_ed25519::ClusterValidatorImpl; 8 | use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 9 | use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; 10 | 11 | /// A Relayer node which can connect to each-other to build a high-available relay system 12 | #[derive(Parser, Debug)] 13 | #[command(author, version, about, long_about = None)] 14 | struct Args { 15 | /// Root domain for generate an unique domain for each agent 16 | #[arg(env, long, default_value = "local.ha.8xff.io")] 17 | root_domain: String, 18 | 19 | /// UDP/TCP port for serving TCP connection from agent 20 | #[arg(env, long, default_value = "0.0.0.0:33330")] 21 | agent_unsecure_listener: SocketAddr, 22 | 23 | /// UDP/TCP port for serving QUIC/TLS connection from agent 24 | #[arg(env, long, default_value = "0.0.0.0:33333")] 25 | agent_secure_listener: SocketAddr, 26 | 27 | /// UDP/TCP port for serving QUIC/TCP connection for SDN network 28 | #[arg(env, long)] 29 | sdn_peer_id: u64, 30 | 31 | /// UDP/TCP port for serving QUIC/TCP connection for SDN network 32 | #[arg(env, long, default_value = "0.0.0.0:11111")] 33 | sdn_listener: SocketAddr, 34 | 35 | /// Seeds with format: node1@IP1:PORT1,node2@IP2:PORT2 36 | #[arg(env, long, value_delimiter = ',')] 37 | sdn_seeds: Vec, 38 | 39 | /// Allow it broadcast address to other peers 40 | /// This allows other peer can active connect to this node 41 | /// This option is useful with high performance relay node 42 | #[arg(env, long)] 43 | sdn_advertise_address: Option, 44 | 45 | /// Shared key for validate network connection 46 | #[arg(env, long, default_value = "insecure")] 47 | sdn_secure_key: String, 48 | 49 | /// TCP port for serving HTTP connection 50 | #[arg(env, long, default_value = "0.0.0.0:80")] 51 | proxy_http_listener: SocketAddr, 52 | 53 | /// TCP port for serving TLS connection 54 | #[arg(env, long, default_value = "0.0.0.0:443")] 55 | proxy_tls_listener: SocketAddr, 56 | 57 | /// TCP port for serving TLS connection 58 | #[arg(env, long, default_value = "0.0.0.0:554")] 59 | proxy_rtsp_listener: SocketAddr, 60 | 61 | /// TCP port for serving TLS connection 62 | #[arg(env, long, default_value = "0.0.0.0:5543")] 63 | proxy_rtsps_listener: SocketAddr, 64 | } 65 | 66 | #[tokio::main] 67 | async fn main() { 68 | rustls::crypto::ring::default_provider().install_default().expect("should install ring as default"); 69 | 70 | if std::env::var_os("RUST_LOG").is_none() { 71 | std::env::set_var("RUST_LOG", "info"); 72 | } 73 | if std::env::var_os("RUST_BACKTRACE").is_none() { 74 | std::env::set_var("RUST_BACKTRACE", "1"); 75 | } 76 | let args: Args = Args::parse(); 77 | tracing_subscriber::registry().with(fmt::layer()).with(EnvFilter::from_default_env()).init(); 78 | 79 | let default_tunnel_cert = CertificateDer::from(DEFAULT_TUNNEL_CERT.to_vec()); 80 | let default_tunnel_key = PrivatePkcs8KeyDer::from(DEFAULT_TUNNEL_KEY.to_vec()); 81 | 82 | let default_cluster_cert = CertificateDer::from(DEFAULT_CLUSTER_CERT.to_vec()); 83 | let default_cluster_key = PrivatePkcs8KeyDer::from(DEFAULT_CLUSTER_KEY.to_vec()); 84 | 85 | let cfg = QuicRelayerConfig { 86 | agent_unsecure_listener: args.agent_unsecure_listener, 87 | agent_secure_listener: args.agent_secure_listener, 88 | proxy_http_listener: args.proxy_http_listener, 89 | proxy_tls_listener: args.proxy_tls_listener, 90 | proxy_rtsp_listener: args.proxy_rtsp_listener, 91 | proxy_rtsps_listener: args.proxy_rtsps_listener, 92 | agent_key: default_tunnel_key, 93 | agent_cert: default_tunnel_cert, 94 | sdn_peer_id: args.sdn_peer_id.into(), 95 | sdn_listener: args.sdn_listener, 96 | sdn_key: default_cluster_key, 97 | sdn_cert: default_cluster_cert, 98 | sdn_seeds: args.sdn_seeds.into_iter().map(|a| a.parse().expect("should parse to PeerAddress")).collect::>(), 99 | sdn_secure: SharedKeyHandshake::from(args.sdn_secure_key.as_str()), 100 | sdn_advertise_address: args.sdn_advertise_address, 101 | tunnel_service_handle: DummyTunnelHandle, 102 | }; 103 | let validator = ClusterValidatorImpl::new(args.root_domain); 104 | let mut relayer = QuicRelayer::new(cfg, validator).await.expect("should create relayer"); 105 | while relayer.recv().await.is_ok() {} 106 | } 107 | 108 | struct DummyTunnelHandle; 109 | 110 | impl TunnelServiceHandle> for DummyTunnelHandle { 111 | fn start(&mut self, _ctx: &atm0s_reverse_proxy_relayer::TunnelServiceCtx) {} 112 | 113 | fn on_agent_conn( 114 | &mut self, 115 | _ctx: &atm0s_reverse_proxy_relayer::TunnelServiceCtx, 116 | _agent_id: protocol::proxy::AgentId, 117 | _metadata: Vec, 118 | _stream: S, 119 | ) { 120 | } 121 | 122 | fn on_cluster_event(&mut self, _ctx: &atm0s_reverse_proxy_relayer::TunnelServiceCtx, _event: p2p::P2pServiceEvent) {} 123 | } 124 | -------------------------------------------------------------------------------- /bin/relayer/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use metrics::{describe_counter, describe_gauge, describe_histogram}; 2 | 3 | // this is for online agent counting 4 | pub const METRICS_AGENT_LIVE: &str = "atm0s_agent_live"; 5 | pub const METRICS_AGENT_HISTOGRAM: &str = "atm0s_agent_histogram"; 6 | pub const METRICS_AGENT_COUNT: &str = "atm0s_agent_count"; 7 | 8 | // this is for proxy from agent counting (incoming) 9 | pub const METRICS_PROXY_AGENT_LIVE: &str = "atm0s_proxy_agent_live"; 10 | pub const METRICS_PROXY_AGENT_COUNT: &str = "atm0s_proxy_agent_count"; 11 | pub const METRICS_PROXY_AGENT_HISTOGRAM: &str = "atm0s_proxy_agent_histogram"; 12 | pub const METRICS_PROXY_AGENT_ERROR_COUNT: &str = "atm0s_proxy_agent_error_count"; 13 | 14 | // this is for http proxy counting (incoming) 15 | pub const METRICS_PROXY_HTTP_LIVE: &str = "atm0s_proxy_http_live"; 16 | pub const METRICS_PROXY_HTTP_COUNT: &str = "atm0s_proxy_http_count"; 17 | pub const METRICS_PROXY_HTTP_ERROR_COUNT: &str = "atm0s_proxy_http_error_count"; 18 | 19 | // this is for cluster proxy (incoming) 20 | pub const METRICS_PROXY_CLUSTER_LIVE: &str = "atm0s_proxy_cluster_live"; 21 | pub const METRICS_PROXY_CLUSTER_COUNT: &str = "atm0s_proxy_cluster_count"; 22 | pub const METRICS_PROXY_CLUSTER_ERROR_COUNT: &str = "atm0s_proxy_cluster_error_count"; 23 | 24 | // this is for tunnel from local node to other node (outgoing) 25 | pub const METRICS_TUNNEL_CLUSTER_LIVE: &str = "atm0s_tunnel_cluster_live"; 26 | pub const METRICS_TUNNEL_CLUSTER_COUNT: &str = "atm0s_tunnel_cluster_count"; 27 | pub const METRICS_TUNNEL_CLUSTER_HISTOGRAM: &str = "atm0s_tunnel_cluster_histogram"; 28 | pub const METRICS_TUNNEL_CLUSTER_ERROR_COUNT: &str = "atm0s_tunnel_cluster_error_count"; 29 | 30 | // this is for tunnel from local node to agent (outgoing) 31 | pub const METRICS_TUNNEL_AGENT_LIVE: &str = "atm0s_tunnel_agent_live"; 32 | pub const METRICS_TUNNEL_AGENT_COUNT: &str = "atm0s_tunnel_agent_count"; 33 | pub const METRICS_TUNNEL_AGENT_HISTOGRAM: &str = "atm0s_tunnel_agent_histogram"; 34 | pub const METRICS_TUNNEL_AGENT_ERROR_COUNT: &str = "atm0s_tunnel_agent_error_count"; 35 | 36 | pub fn describe_metrics() { 37 | // this is for online agent counting 38 | describe_gauge!(METRICS_AGENT_LIVE, "Live agent count"); 39 | describe_histogram!(METRICS_AGENT_HISTOGRAM, "Incoming agent connection accept time histogram"); 40 | describe_counter!(METRICS_AGENT_COUNT, "Number of connected agents"); 41 | 42 | // this is for proxy from agent counting (incoming) 43 | describe_gauge!(METRICS_PROXY_AGENT_LIVE, "Live incoming proxy from agent to cluster"); 44 | describe_counter!(METRICS_PROXY_AGENT_COUNT, "Number of incoming proxy from agent to cluster"); 45 | describe_histogram!(METRICS_PROXY_AGENT_HISTOGRAM, "Incoming proxy from agent to cluster latency histogram"); 46 | describe_counter!(METRICS_PROXY_AGENT_ERROR_COUNT, "Number of incoming proxy error from agent to cluster"); 47 | 48 | // this is for http proxy counting (incoming) 49 | describe_gauge!(METRICS_PROXY_HTTP_LIVE, "Live incoming http proxy"); 50 | describe_counter!(METRICS_PROXY_HTTP_COUNT, "Number of incoming http proxy"); 51 | describe_counter!(METRICS_PROXY_HTTP_ERROR_COUNT, "Number of incoming http proxy error"); 52 | 53 | // this is for cluster proxy (incoming) 54 | describe_gauge!(METRICS_PROXY_CLUSTER_LIVE, "Live incoming cluster proxy"); 55 | describe_counter!(METRICS_PROXY_CLUSTER_COUNT, "Number of incoming cluster proxy"); 56 | describe_counter!(METRICS_PROXY_CLUSTER_ERROR_COUNT, "Number of incoming cluster proxy error"); 57 | 58 | // this is for tunnel from local node to other node (outgoing) 59 | describe_gauge!(METRICS_TUNNEL_CLUSTER_LIVE, "Live outgoing tunnel to cluster"); 60 | describe_counter!(METRICS_TUNNEL_CLUSTER_COUNT, "Number of outgoing tunnel to cluster"); 61 | describe_histogram!(METRICS_TUNNEL_CLUSTER_HISTOGRAM, "Outgoing tunnel to cluster latency histogram"); 62 | describe_counter!(METRICS_TUNNEL_CLUSTER_ERROR_COUNT, "Number of outgoing tunnel to cluster error"); 63 | 64 | // this is for tunnel from local node to agent (outgoing) 65 | describe_gauge!(METRICS_TUNNEL_AGENT_LIVE, "Live outgoing tunnel to agent"); 66 | describe_counter!(METRICS_TUNNEL_AGENT_COUNT, "Number of outgoing tunnel to agent"); 67 | describe_counter!(METRICS_TUNNEL_AGENT_HISTOGRAM, "Outgoing tunnel to agent latency histogram"); 68 | describe_counter!(METRICS_TUNNEL_AGENT_ERROR_COUNT, "Number of outgoing tunnel to agent error"); 69 | } 70 | -------------------------------------------------------------------------------- /bin/relayer/src/proxy.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc}; 2 | 3 | use protocol::proxy::ProxyDestination; 4 | use tokio::{ 5 | net::{TcpListener, TcpStream}, 6 | select, 7 | sync::mpsc::{channel, Receiver, Sender}, 8 | }; 9 | 10 | pub mod http; 11 | pub mod rtsp; 12 | pub mod tls; 13 | 14 | pub trait ProxyDestinationDetector: Send + Sync + 'static { 15 | fn determine(&self, stream: &mut TcpStream) -> impl std::future::Future> + Send; 16 | } 17 | 18 | pub struct ProxyTcpListener { 19 | listener: TcpListener, 20 | detector: Arc, 21 | rx: Receiver<(ProxyDestination, TcpStream)>, 22 | tx: Sender<(ProxyDestination, TcpStream)>, 23 | } 24 | 25 | impl ProxyTcpListener { 26 | pub async fn new(addr: SocketAddr, detector: Detector) -> anyhow::Result { 27 | let (tx, rx) = channel(10); 28 | Ok(Self { 29 | detector: detector.into(), 30 | listener: TcpListener::bind(addr).await?, 31 | tx, 32 | rx, 33 | }) 34 | } 35 | 36 | pub async fn recv(&mut self) -> anyhow::Result<(ProxyDestination, TcpStream)> { 37 | loop { 38 | select! { 39 | event = self.listener.accept() => { 40 | let (mut stream, remote) = event?; 41 | let tx = self.tx.clone(); 42 | let detector = self.detector.clone(); 43 | tokio::spawn(async move { 44 | match detector.determine(&mut stream).await { 45 | Ok(destination) => { 46 | log::info!("[ProxyTcpListener] determine destination {}, service {:?} for remote {remote}", destination.domain, destination.service); 47 | tx.send((destination, stream)).await.expect("tcp listener channel should work"); 48 | }, 49 | Err(err) => { 50 | log::info!("[ProxyTcpListener] determine destination for {remote} error {err}"); 51 | }, 52 | } 53 | }); 54 | }, 55 | out = self.rx.recv() => { 56 | break Ok(out.expect("tcp listener channel should work")) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bin/relayer/src/proxy/http.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use tokio::net::TcpStream; 3 | 4 | use super::{ProxyDestination, ProxyDestinationDetector}; 5 | 6 | #[derive(Debug, Default)] 7 | pub struct HttpDestinationDetector {} 8 | 9 | impl ProxyDestinationDetector for HttpDestinationDetector { 10 | async fn determine(&self, stream: &mut TcpStream) -> anyhow::Result { 11 | let mut buf = [0; 4096]; 12 | let buf_len = stream.peek(&mut buf).await?; 13 | log::info!("[HttpDomainDetector] check domain for {}", String::from_utf8_lossy(&buf[..buf_len])); 14 | let mut headers = [httparse::EMPTY_HEADER; 64]; 15 | let mut req = httparse::Request::new(&mut headers); 16 | let _ = req.parse(&buf[..buf_len])?; 17 | let domain = req.headers.iter().find(|h| h.name.to_lowercase() == "host").ok_or(anyhow!("host header missing"))?.value; 18 | // dont get the port 19 | let domain = String::from_utf8_lossy(domain); 20 | let domain = domain.split(':').next().expect("should have domain"); 21 | Ok(ProxyDestination { 22 | domain: domain.to_string(), 23 | service: None, 24 | tls: false, 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bin/relayer/src/proxy/rtsp.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use protocol::services::SERVICE_RTSP; 3 | use tokio::net::TcpStream; 4 | 5 | use super::{ProxyDestination, ProxyDestinationDetector}; 6 | 7 | #[derive(Debug, Default)] 8 | pub struct RtspDestinationDetector {} 9 | 10 | impl ProxyDestinationDetector for RtspDestinationDetector { 11 | async fn determine(&self, stream: &mut TcpStream) -> anyhow::Result { 12 | let mut buf = [0; 4096]; 13 | let buf_len = stream.peek(&mut buf).await?; 14 | log::info!("[RtspDomainDetector] check domain for {}", String::from_utf8_lossy(&buf[..buf_len])); 15 | let (message, _consumed): (rtsp_types::Message>, _) = rtsp_types::Message::parse(&buf[..buf_len])?; 16 | log::info!("{:?}", message); 17 | match message { 18 | rtsp_types::Message::Request(req) => Ok(ProxyDestination { 19 | domain: req 20 | .request_uri() 21 | .ok_or(anyhow!("missing request uri"))? 22 | .host() 23 | .map(|h| h.to_string()) 24 | .ok_or(anyhow!("missing host header"))?, 25 | service: Some(SERVICE_RTSP), 26 | tls: false, 27 | }), 28 | _ => Err(anyhow!("invalid rtsp message")), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bin/relayer/src/proxy/tls.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use tls_parser::{parse_tls_extensions, parse_tls_plaintext, TlsExtension, TlsMessage, TlsMessageHandshake}; 3 | use tokio::net::TcpStream; 4 | 5 | use super::{ProxyDestination, ProxyDestinationDetector}; 6 | 7 | #[derive(Debug, Default)] 8 | pub struct TlsDestinationDetector { 9 | service: Option, 10 | } 11 | 12 | impl TlsDestinationDetector { 13 | pub fn custom_service(service: u16) -> Self { 14 | Self { service: Some(service) } 15 | } 16 | 17 | fn get_domain(&self, packet: &[u8]) -> Option { 18 | log::info!("[TlsDomainDetector] check domain for buffer {} bytes", packet.len()); 19 | let res = match parse_tls_plaintext(packet) { 20 | Ok(res) => res, 21 | Err(e) => { 22 | log::error!("parse_tls_plaintext error {:?}", e); 23 | return None; 24 | } 25 | }; 26 | 27 | let tls_message = &res.1.msg[0]; 28 | if let TlsMessage::Handshake(TlsMessageHandshake::ClientHello(client_hello)) = tls_message { 29 | // get the extensions 30 | let extensions: &[u8] = client_hello.ext?; 31 | // parse the extensions 32 | let res = match parse_tls_extensions(extensions) { 33 | Ok(res) => res, 34 | Err(e) => { 35 | log::error!("parse_tls_extensions error {:?}", e); 36 | return None; 37 | } 38 | }; 39 | // iterate over the extensions and find the SNI 40 | for extension in res.1 { 41 | if let TlsExtension::SNI(sni) = extension { 42 | // get the hostname 43 | let hostname: &[u8] = sni[0].1; 44 | let s: String = match String::from_utf8(hostname.to_vec()) { 45 | Ok(v) => v, 46 | Err(e) => panic!("Invalid UTF-8 sequence: {}", e), 47 | }; 48 | return Some(s); 49 | } 50 | } 51 | } 52 | None 53 | } 54 | } 55 | 56 | impl ProxyDestinationDetector for TlsDestinationDetector { 57 | async fn determine(&self, stream: &mut TcpStream) -> anyhow::Result { 58 | let mut buf = [0; 4096]; 59 | let buf_len = stream.peek(&mut buf).await?; 60 | let domain = self.get_domain(&buf[..buf_len]).ok_or(anyhow!("domain not found"))?; 61 | Ok(ProxyDestination { 62 | domain, 63 | service: self.service, 64 | tls: true, 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /bin/relayer/src/quic.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc, time::Duration}; 2 | 3 | use protocol::stream::TunnelStream; 4 | use quinn::{ClientConfig, Endpoint, RecvStream, SendStream, ServerConfig, TransportConfig}; 5 | use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 6 | 7 | pub type TunnelQuicStream = TunnelStream; 8 | 9 | pub fn make_server_endpoint(bind_addr: SocketAddr, priv_key: PrivatePkcs8KeyDer<'static>, cert: CertificateDer<'static>) -> anyhow::Result { 10 | let server_config = configure_server(priv_key, cert.clone())?; 11 | let client_config = configure_client(&[cert])?; 12 | let mut endpoint = Endpoint::server(server_config, bind_addr)?; 13 | endpoint.set_default_client_config(client_config); 14 | Ok(endpoint) 15 | } 16 | 17 | /// Returns default server configuration along with its certificate. 18 | fn configure_server(priv_key: PrivatePkcs8KeyDer<'static>, cert: CertificateDer<'static>) -> anyhow::Result { 19 | let cert_chain = vec![cert]; 20 | 21 | let mut server_config = ServerConfig::with_single_cert(cert_chain, priv_key.into())?; 22 | let transport_config = Arc::get_mut(&mut server_config.transport).expect("Should get transport config mut right after create server config"); 23 | transport_config.max_concurrent_uni_streams(0_u8.into()); 24 | transport_config.max_idle_timeout(Some(Duration::from_secs(30).try_into().expect("Should config timeout"))); 25 | 26 | Ok(server_config) 27 | } 28 | 29 | fn configure_client(server_certs: &[CertificateDer]) -> anyhow::Result { 30 | let mut certs = rustls::RootCertStore::empty(); 31 | for cert in server_certs { 32 | certs.add(cert.clone())?; 33 | } 34 | let mut config = ClientConfig::with_root_certificates(Arc::new(certs))?; 35 | 36 | let mut transport = TransportConfig::default(); 37 | transport.keep_alive_interval(Some(Duration::from_secs(15))); 38 | transport.max_idle_timeout(Some(Duration::from_secs(30).try_into().expect("Should config timeout"))); 39 | config.transport_config(Arc::new(transport)); 40 | Ok(config) 41 | } 42 | -------------------------------------------------------------------------------- /crates/cert_utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-cert-utils-v0.1.0...atm0s-reverse-proxy-cert-utils-v0.1.1) - 2024-10-22 10 | 11 | ### Fixed 12 | 13 | - agent id generate crash ([#74](https://github.com/8xFF/atm0s-reverse-proxy/pull/74)) 14 | 15 | ### Other 16 | 17 | - small-sdn with quic ([#70](https://github.com/8xFF/atm0s-reverse-proxy/pull/70)) 18 | 19 | ## [0.1.0](https://github.com/8xFF/atm0s-reverse-proxy/releases/tag/atm0s-reverse-proxy-cert-utils-v0.1.0) - 2024-08-17 20 | 21 | ### Fixed 22 | - release action error ([#26](https://github.com/8xFF/atm0s-reverse-proxy/pull/26)) 23 | 24 | ### Other 25 | - added release-plz and update deps ([#39](https://github.com/8xFF/atm0s-reverse-proxy/pull/39)) 26 | -------------------------------------------------------------------------------- /crates/cert_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-reverse-proxy-cert-utils" 3 | version = "0.1.1" 4 | edition = "2021" 5 | description = "Cert utils for atm0s-reverse proxy cluster" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rcgen = { workspace = true } 12 | rustls = { workspace = true } 13 | clap = { workspace = true, features = ["derive", "env"] } 14 | -------------------------------------------------------------------------------- /crates/cert_utils/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | use clap::Parser; 4 | 5 | /// A Certs util for quic, which generate der cert and key based on domain 6 | #[derive(Parser, Debug)] 7 | #[command(author, version, about, long_about = None)] 8 | struct Args { 9 | /// Domains 10 | #[arg(env, long)] 11 | domains: Vec, 12 | } 13 | 14 | fn main() { 15 | let args = Args::parse(); 16 | let cert = rcgen::generate_simple_self_signed(args.domains).expect("Should generate cert"); 17 | let start = SystemTime::now(); 18 | let since_the_epoch = start.duration_since(UNIX_EPOCH).expect("Time went backwards").as_millis(); 19 | std::fs::write(format!("./certificate-{}.cert", since_the_epoch), cert.cert.der()).expect("Should write cert"); 20 | std::fs::write(format!("./certificate-{}.key", since_the_epoch), cert.key_pair.serialize_der()).expect("Should write key"); 21 | } 22 | -------------------------------------------------------------------------------- /crates/protocol/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.3.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-v0.3.0...atm0s-reverse-proxy-protocol-v0.3.1) - 2025-02-14 10 | 11 | ### Added 12 | 13 | - add agent context uses in forward from agent to service (#90) 14 | 15 | ## [0.3.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-v0.2.1...atm0s-reverse-proxy-protocol-v0.3.0) - 2024-10-22 16 | 17 | ### Fixed 18 | 19 | - agent id generate crash ([#74](https://github.com/8xFF/atm0s-reverse-proxy/pull/74)) 20 | 21 | ### Other 22 | 23 | - small-sdn with quic ([#70](https://github.com/8xFF/atm0s-reverse-proxy/pull/70)) 24 | 25 | ## [0.2.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-v0.2.0...atm0s-reverse-proxy-protocol-v0.2.1) - 2024-10-04 26 | 27 | ### Fixed 28 | 29 | - full cpu with pipe-streams when writer Pending in long time ([#65](https://github.com/8xFF/atm0s-reverse-proxy/pull/65)) 30 | - tunnel stuck ([#63](https://github.com/8xFF/atm0s-reverse-proxy/pull/63)) 31 | 32 | ### Other 33 | 34 | - switched to tokio ([#66](https://github.com/8xFF/atm0s-reverse-proxy/pull/66)) 35 | 36 | ## [0.2.0](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-v0.1.1...atm0s-reverse-proxy-protocol-v0.2.0) - 2024-09-09 37 | 38 | ### Fixed 39 | 40 | - wrong handshake when tunnel request over cluster ([#48](https://github.com/8xFF/atm0s-reverse-proxy/pull/48)) 41 | 42 | ## [0.1.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-v0.1.0...atm0s-reverse-proxy-protocol-v0.1.1) - 2024-08-17 43 | 44 | ### Other 45 | - fix release-plz don't found default cert ([#42](https://github.com/8xFF/atm0s-reverse-proxy/pull/42)) 46 | -------------------------------------------------------------------------------- /crates/protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-reverse-proxy-protocol" 3 | version = "0.3.1" 4 | edition = "2021" 5 | description = "Protocol for atm0s-reverse proxy cluster" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | serde = { workspace = true, features = ["derive"] } 12 | bincode = { workspace = true } 13 | tokio = { workspace = true, features = ["io-util"] } 14 | log = { workspace = true } 15 | anyhow = { workspace = true } 16 | tokio-util = { workspace = true, features = ["codec"] } 17 | derive_more = { workspace = true, features = ["from", "into", "deref"] } 18 | -------------------------------------------------------------------------------- /crates/protocol/certs/cluster.cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-reverse-proxy/b3a977328f4cde4cd2a5b38f293f5b3ef67fe420/crates/protocol/certs/cluster.cert -------------------------------------------------------------------------------- /crates/protocol/certs/cluster.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-reverse-proxy/b3a977328f4cde4cd2a5b38f293f5b3ef67fe420/crates/protocol/certs/cluster.key -------------------------------------------------------------------------------- /crates/protocol/certs/tunnel.cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-reverse-proxy/b3a977328f4cde4cd2a5b38f293f5b3ef67fe420/crates/protocol/certs/tunnel.cert -------------------------------------------------------------------------------- /crates/protocol/certs/tunnel.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8xFF/atm0s-reverse-proxy/b3a977328f4cde4cd2a5b38f293f5b3ef67fe420/crates/protocol/certs/tunnel.key -------------------------------------------------------------------------------- /crates/protocol/src/cluster.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 3 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub struct ClusterTunnelRequest { 7 | pub domain: String, 8 | pub handshake: Vec, 9 | } 10 | 11 | impl From<&ClusterTunnelRequest> for Vec { 12 | fn from(resp: &ClusterTunnelRequest) -> Self { 13 | bincode::serialize(resp).expect("Should serialize cluster tunnel request") 14 | } 15 | } 16 | 17 | impl TryFrom<&[u8]> for ClusterTunnelRequest { 18 | type Error = bincode::Error; 19 | 20 | fn try_from(buf: &[u8]) -> Result { 21 | bincode::deserialize(buf) 22 | } 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize)] 26 | pub struct ClusterTunnelResponse { 27 | pub success: bool, 28 | } 29 | 30 | impl From<&ClusterTunnelResponse> for Vec { 31 | fn from(resp: &ClusterTunnelResponse) -> Self { 32 | bincode::serialize(resp).expect("Should serialize cluster tunnel response") 33 | } 34 | } 35 | 36 | impl TryFrom<&[u8]> for ClusterTunnelResponse { 37 | type Error = bincode::Error; 38 | 39 | fn try_from(buf: &[u8]) -> Result { 40 | bincode::deserialize(buf) 41 | } 42 | } 43 | 44 | #[derive(Debug, Serialize, Deserialize)] 45 | pub struct AgentTunnelRequest { 46 | pub service: Option, 47 | pub tls: bool, 48 | pub domain: String, 49 | } 50 | 51 | impl From<&AgentTunnelRequest> for Vec { 52 | fn from(resp: &AgentTunnelRequest) -> Self { 53 | bincode::serialize(resp).expect("Should serialize agent tunnel request") 54 | } 55 | } 56 | 57 | impl TryFrom<&[u8]> for AgentTunnelRequest { 58 | type Error = bincode::Error; 59 | 60 | fn try_from(buf: &[u8]) -> Result { 61 | bincode::deserialize(buf) 62 | } 63 | } 64 | 65 | pub async fn wait_object(reader: &mut R) -> anyhow::Result { 66 | let mut len_buf = [0; 2]; 67 | let mut data_buf = [0; MAX_SIZE]; 68 | reader.read_exact(&mut len_buf).await?; 69 | let handshake_len = u16::from_be_bytes([len_buf[0], len_buf[1]]) as usize; 70 | if handshake_len > data_buf.len() { 71 | return Err(anyhow!("packet to big {} vs {MAX_SIZE}", data_buf.len())); 72 | } 73 | 74 | reader.read_exact(&mut data_buf[0..handshake_len]).await?; 75 | 76 | Ok(bincode::deserialize(&data_buf[0..handshake_len])?) 77 | } 78 | 79 | pub async fn write_object(writer: &mut W, object: &O) -> anyhow::Result<()> { 80 | let data_buf: Vec = bincode::serialize(object).expect("Should convert to binary"); 81 | if data_buf.len() > MAX_SIZE { 82 | return Err(anyhow!("buffer to big {} vs {MAX_SIZE}", data_buf.len())); 83 | } 84 | let len_buf = (data_buf.len() as u16).to_be_bytes(); 85 | 86 | writer.write_all(&len_buf).await?; 87 | writer.write_all(&data_buf).await?; 88 | Ok(()) 89 | } 90 | 91 | pub async fn wait_buf(reader: &mut R) -> anyhow::Result> { 92 | let mut len_buf = [0; 2]; 93 | let mut data_buf = [0; MAX_SIZE]; 94 | reader.read_exact(&mut len_buf).await?; 95 | let handshake_len = u16::from_be_bytes([len_buf[0], len_buf[1]]) as usize; 96 | if handshake_len > data_buf.len() { 97 | return Err(anyhow!("packet to big {} vs {MAX_SIZE}", data_buf.len())); 98 | } 99 | 100 | reader.read_exact(&mut data_buf[0..handshake_len]).await?; 101 | 102 | Ok(data_buf[0..handshake_len].to_vec()) 103 | } 104 | 105 | pub async fn write_buf(writer: &mut W, data_buf: &[u8]) -> anyhow::Result<()> { 106 | if data_buf.len() > MAX_SIZE { 107 | return Err(anyhow!("buffer to big {} vs {MAX_SIZE}", data_buf.len())); 108 | } 109 | let len_buf = (data_buf.len() as u16).to_be_bytes(); 110 | 111 | writer.write_all(&len_buf).await?; 112 | writer.write_all(data_buf).await?; 113 | Ok(()) 114 | } 115 | -------------------------------------------------------------------------------- /crates/protocol/src/key.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | pub trait ClusterRequest { 4 | type Context: Clone + Send + Sync + 'static + Debug; 5 | fn context(&self) -> Self::Context; 6 | } 7 | 8 | pub trait AgentSigner { 9 | fn sign_connect_req(&self) -> Vec; 10 | fn validate_connect_res(&self, resp: &[u8]) -> anyhow::Result; 11 | } 12 | 13 | pub trait ClusterValidator: Send + Sync + Clone + 'static { 14 | fn validate_connect_req(&self, req: &[u8]) -> anyhow::Result; 15 | fn generate_domain(&self, req: &REQ) -> anyhow::Result; 16 | fn sign_response_res(&self, m: &REQ, err: Option) -> Vec; 17 | } 18 | -------------------------------------------------------------------------------- /crates/protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cluster; 2 | pub mod key; 3 | pub mod proxy; 4 | pub mod services; 5 | pub mod stream; 6 | pub mod time; 7 | 8 | pub const DEFAULT_TUNNEL_CERT: &[u8] = include_bytes!("../certs/tunnel.cert"); 9 | pub const DEFAULT_TUNNEL_KEY: &[u8] = include_bytes!("../certs/tunnel.key"); 10 | 11 | pub const DEFAULT_CLUSTER_CERT: &[u8] = include_bytes!("../certs/cluster.cert"); 12 | pub const DEFAULT_CLUSTER_KEY: &[u8] = include_bytes!("../certs/cluster.key"); 13 | -------------------------------------------------------------------------------- /crates/protocol/src/proxy.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use anyhow::anyhow; 4 | use derive_more::derive::{Deref, From}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Debug, Hash, PartialEq, Eq, From, Deref, Clone, Copy)] 8 | pub struct AgentId(u64); 9 | 10 | impl Display for AgentId { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | f.write_fmt(format_args!("AgentId({:02x})", self.0)) 13 | } 14 | } 15 | 16 | impl AgentId { 17 | pub fn try_from_domain(domain: &str) -> anyhow::Result { 18 | let (first, _) = domain.as_bytes().split_at_checked(8).ok_or(anyhow!("domain should be at least 8 bytes"))?; 19 | Ok(Self(u64::from_be_bytes(first.try_into()?))) 20 | } 21 | } 22 | 23 | #[derive(Debug, Serialize, Deserialize)] 24 | pub struct ProxyDestination { 25 | pub domain: String, 26 | pub service: Option, 27 | pub tls: bool, 28 | } 29 | 30 | impl ProxyDestination { 31 | pub fn agent_id(&self) -> anyhow::Result { 32 | AgentId::try_from_domain(&self.domain) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/protocol/src/services.rs: -------------------------------------------------------------------------------- 1 | pub const SERVICE_RTSP: u16 = 554; 2 | -------------------------------------------------------------------------------- /crates/protocol/src/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use tokio::io::{AsyncRead, AsyncWrite}; 8 | 9 | pub struct TunnelStream { 10 | read: R, 11 | write: W, 12 | } 13 | 14 | impl TunnelStream { 15 | pub fn new(read: R, write: W) -> Self { 16 | Self { read, write } 17 | } 18 | } 19 | 20 | impl AsyncRead for TunnelStream { 21 | fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut tokio::io::ReadBuf<'_>) -> Poll> { 22 | Pin::new(&mut self.get_mut().read).poll_read(cx, buf) 23 | } 24 | } 25 | 26 | impl AsyncWrite for TunnelStream { 27 | fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { 28 | Pin::new(&mut self.get_mut().write).poll_write(cx, buf) 29 | } 30 | 31 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 32 | Pin::new(&mut self.get_mut().write).poll_flush(cx) 33 | } 34 | 35 | fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 36 | Pin::new(&mut self.get_mut().write).poll_shutdown(cx) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/protocol/src/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | pub fn now_ms() -> u64 { 4 | // Get the current time 5 | let now = SystemTime::now(); 6 | 7 | // Calculate the duration since the UNIX epoch 8 | let duration = now.duration_since(UNIX_EPOCH).expect("Time went backwards"); 9 | 10 | // Return the total milliseconds 11 | duration.as_millis() as u64 12 | } 13 | -------------------------------------------------------------------------------- /crates/protocol_ed25519/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.5](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-ed25519-v0.1.4...atm0s-reverse-proxy-protocol-ed25519-v0.1.5) - 2025-02-14 10 | 11 | ### Added 12 | 13 | - add agent context uses in forward from agent to service (#90) 14 | 15 | ## [0.1.4](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-ed25519-v0.1.3...atm0s-reverse-proxy-protocol-ed25519-v0.1.4) - 2024-10-22 16 | 17 | ### Other 18 | 19 | - small-sdn with quic ([#70](https://github.com/8xFF/atm0s-reverse-proxy/pull/70)) 20 | 21 | ## [0.1.3](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-ed25519-v0.1.2...atm0s-reverse-proxy-protocol-ed25519-v0.1.3) - 2024-10-04 22 | 23 | ### Other 24 | 25 | - switched to tokio ([#66](https://github.com/8xFF/atm0s-reverse-proxy/pull/66)) 26 | 27 | ## [0.1.2](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-ed25519-v0.1.1...atm0s-reverse-proxy-protocol-ed25519-v0.1.2) - 2024-09-09 28 | 29 | ### Other 30 | 31 | - updated the following local packages: atm0s-reverse-proxy-protocol 32 | 33 | ## [0.1.1](https://github.com/8xFF/atm0s-reverse-proxy/compare/atm0s-reverse-proxy-protocol-ed25519-v0.1.0...atm0s-reverse-proxy-protocol-ed25519-v0.1.1) - 2024-08-17 34 | 35 | ### Other 36 | - updated the following local packages: atm0s-reverse-proxy-protocol 37 | -------------------------------------------------------------------------------- /crates/protocol_ed25519/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atm0s-reverse-proxy-protocol-ed25519" 3 | version = "0.1.5" 4 | edition = "2021" 5 | description = "Protocol implement with Ed25519 for atm0s-reverse proxy cluster" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | protocol = { workspace = true } 12 | bincode = { workspace = true } 13 | ed25519-dalek = { workspace = true, features = ["rand_core", "serde", "pkcs8", "pem"] } 14 | rand = { workspace = true } 15 | serde = { workspace = true, features = ["derive"] } 16 | anyhow = { workspace = true } 17 | -------------------------------------------------------------------------------- /crates/protocol_ed25519/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; 2 | use ed25519_dalek::pkcs8::{DecodePrivateKey, EncodePrivateKey}; 3 | use ed25519_dalek::SigningKey; 4 | use ed25519_dalek::{Signer, Verifier}; 5 | use protocol::key::{AgentSigner, ClusterRequest, ClusterValidator}; 6 | use rand::rngs::OsRng; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] 10 | pub struct RegisterRequest { 11 | pub pub_key: ed25519_dalek::VerifyingKey, 12 | pub signature: ed25519_dalek::Signature, 13 | } 14 | 15 | impl ClusterRequest for RegisterRequest { 16 | type Context = Vec; 17 | 18 | fn context(&self) -> Self::Context { 19 | self.pub_key.to_bytes().to_vec() 20 | } 21 | } 22 | 23 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] 24 | pub struct RegisterResponse { 25 | pub response: Result, 26 | } 27 | 28 | pub struct AgentLocalKey { 29 | sign_key: SigningKey, 30 | } 31 | 32 | impl AgentLocalKey { 33 | pub fn random() -> Self { 34 | let mut csprng = OsRng; 35 | Self { 36 | sign_key: SigningKey::generate(&mut csprng), 37 | } 38 | } 39 | 40 | pub fn from_buf(buf: &[u8]) -> Option { 41 | let buf: &[u8; 32] = buf.try_into().ok()?; 42 | Some(Self { 43 | sign_key: SigningKey::from_bytes(buf), 44 | }) 45 | } 46 | 47 | pub fn to_buf(&self) -> Vec { 48 | self.sign_key.to_bytes().to_vec() 49 | } 50 | 51 | pub fn from_pem(buf: &str) -> Option { 52 | let key = SigningKey::from_pkcs8_pem(buf).ok()?; 53 | Some(Self { sign_key: key }) 54 | } 55 | 56 | pub fn to_pem(&self) -> String { 57 | let key = self.sign_key.to_pkcs8_pem(LineEnding::CRLF).expect("Should ok"); 58 | key.to_string() 59 | } 60 | } 61 | 62 | impl AgentSigner for AgentLocalKey { 63 | fn sign_connect_req(&self) -> Vec { 64 | let msg = self.sign_key.verifying_key().to_bytes(); 65 | let signature = self.sign_key.sign(&msg); 66 | 67 | bincode::serialize(&RegisterRequest { 68 | pub_key: self.sign_key.verifying_key(), 69 | signature, 70 | }) 71 | .expect("should serialize") 72 | .to_vec() 73 | } 74 | 75 | fn validate_connect_res(&self, resp: &[u8]) -> anyhow::Result { 76 | Ok(bincode::deserialize(resp)?) 77 | } 78 | } 79 | 80 | #[derive(Clone)] 81 | pub struct ClusterValidatorImpl { 82 | root_domain: String, 83 | } 84 | 85 | impl ClusterValidatorImpl { 86 | pub fn new(root_domain: String) -> Self { 87 | Self { root_domain } 88 | } 89 | } 90 | 91 | impl ClusterValidator for ClusterValidatorImpl { 92 | fn validate_connect_req(&self, req: &[u8]) -> anyhow::Result { 93 | let req: RegisterRequest = bincode::deserialize(req)?; 94 | let msg = req.pub_key.to_bytes(); 95 | req.pub_key.verify(&msg, &req.signature)?; 96 | Ok(req) 97 | } 98 | 99 | fn generate_domain(&self, req: &RegisterRequest) -> anyhow::Result { 100 | Ok(format!("{}.{}", convert_hex(&req.pub_key.to_bytes()[0..16]), self.root_domain)) 101 | } 102 | 103 | fn sign_response_res(&self, m: &RegisterRequest, err: Option) -> Vec { 104 | if let Some(err) = err { 105 | bincode::serialize(&RegisterResponse { response: Err(err) }).expect("should serialize").to_vec() 106 | } else { 107 | bincode::serialize(&RegisterResponse { 108 | response: Ok(format!("{}.{}", convert_hex(&m.pub_key.to_bytes()[0..16]), self.root_domain)), 109 | }) 110 | .expect("should serialize") 111 | .to_vec() 112 | } 113 | } 114 | } 115 | 116 | fn convert_hex(buf: &[u8]) -> String { 117 | let mut s = String::new(); 118 | for b in buf { 119 | s.push_str(&format!("{:02x}", b)); 120 | } 121 | s 122 | } 123 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | 3 | targets = [ 4 | ] 5 | all-features = false 6 | no-default-features = false 7 | [output] 8 | feature-depth = 1 9 | 10 | [advisories] 11 | ignore = [ 12 | ] 13 | 14 | [licenses] 15 | unlicensed = "allow" 16 | allow = [ 17 | "Apache-2.0", 18 | "BSD-2-Clause", 19 | "BSD-3-Clause", 20 | "ISC", 21 | "MIT", 22 | "Unicode-DFS-2016", 23 | "WTFPL", 24 | "OpenSSL", 25 | "Zlib", 26 | "Unicode-3.0" 27 | ] 28 | confidence-threshold = 0.8 29 | exceptions = [ 30 | ] 31 | 32 | 33 | [licenses.private] 34 | ignore = false 35 | registries = [ 36 | #"https://sekretz.com/registry 37 | ] 38 | 39 | [bans] 40 | multiple-versions = "warn" 41 | wildcards = "allow" 42 | highlight = "all" 43 | workspace-default-features = "allow" 44 | external-default-features = "allow" 45 | allow = [ 46 | ] 47 | deny = [ 48 | ] 49 | 50 | skip = [ 51 | ] 52 | skip-tree = [ 53 | ] 54 | [sources] 55 | unknown-registry = "warn" 56 | unknown-git = "warn" 57 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 58 | allow-git = [] 59 | 60 | [sources.allow-org] 61 | github = [""] 62 | gitlab = [""] 63 | bitbucket = [""] 64 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 200 2 | single_line_if_else_max_width = 20 3 | short_array_element_width_threshold = 20 --------------------------------------------------------------------------------