[
  {
    "path": ".dockerignore",
    "content": "# Directories\n/.git/\n/.github/\n/target/\n/examples/\n/docs/\n/benches/\n\n# Files\n.gitignore\n*.md\nLICENSE\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n<!-- Please try the latest release before filing a bug report -->\n\n**Describe the bug**\n<!-- A clear and concise description of what the bug is. -->\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. \n2. \n\n**Configuration**\nConfiguration used to reproduce the behavior:\n\n**Logs**\n<!-- Please upload full client and server logs if possible, with sensitive information masked.\nIf you encountered a panic, please re-run with `RUST_BACKTRACE=1` to provide the backtrace. -->\n\n**Environment:**\n - OS: <!-- Please fill in distribution if you're using linux-->\n- `rathole --version` output: \n- CPU architecture:\n- rustc version:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: Ask for a new feature\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Feature Proposed**\n<!-- describe the feature -->\n\n**Use Case**\n<!-- possible use case -->\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - \"*\"\n\n  workflow_dispatch:\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  release:\n    name: Cross build for ${{ matrix.target }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            exe: rathole\n            cross: false\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n            exe: rathole\n            cross: false\n          - os: ubuntu-latest\n            target: aarch64-unknown-linux-musl\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: arm-unknown-linux-musleabi\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: arm-unknown-linux-musleabihf\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: armv7-unknown-linux-musleabihf\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: mips-unknown-linux-gnu\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: mips-unknown-linux-musl\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: mipsel-unknown-linux-gnu\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: mipsel-unknown-linux-musl\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: mips64-unknown-linux-gnuabi64\n            exe: rathole\n            cross: true\n          - os: ubuntu-latest\n            target: mips64el-unknown-linux-gnuabi64\n            exe: rathole\n            cross: true\n\n          - os: macos-latest\n            target: x86_64-apple-darwin\n            exe: rathole\n            cross: false\n          \n          - os: macos-latest\n            target: aarch64-apple-darwin\n            exe: rathole\n            cross: false\n\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n            exe: rathole.exe\n            cross: false\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          default: true\n      \n      - name: Install OpenSSL\n        if: matrix.os == 'ubuntu-latest'\n        run: sudo apt-get install pkg-config libssl-dev\n      - name: Install OpenSSL\n        if: matrix.os == 'macos-latest'\n        run: brew install openssl@3\n      \n      # Native build\n      - name: Install target\n        if: matrix.cross == false\n        run: rustup target add ${{ matrix.target }}\n      - name: Run tests\n        if: matrix.cross == false && matrix.target != 'aarch64-apple-darwin'\n        run: cargo test --release --target ${{ matrix.target }} --verbose \n      - name: Build release\n        if: matrix.cross == false \n        run: cargo build --release --target ${{ matrix.target }}\n\n      # Cross build\n      - name: Install cross\n        if: matrix.cross\n        run: cargo install --version 0.2.5 cross\n      - name: Run tests\n        if: matrix.cross\n        run: cross test --release --target ${{ matrix.target }} --verbose --features embedded --no-default-features\n      - name: Build release\n        if: matrix.cross\n        run: cross build --release --target ${{ matrix.target }} --features embedded --no-default-features\n\n      - name: Run UPX\n        # Upx may not support some platforms. Ignore the errors\n        continue-on-error: true\n        # Disable upx for mips. See https://github.com/upx/upx/issues/387\n        if: matrix.os == 'ubuntu-latest' && !contains(matrix.target, 'mips')\n        uses: crazy-max/ghaction-upx@v1\n        with:\n          version: v4.0.2\n          files: target/${{ matrix.target }}/release/${{ matrix.exe }}\n          args: -q --best --lzma\n      - uses: actions/upload-artifact@v4\n        with:\n          name: rathole-${{ matrix.target }}\n          path: target/${{ matrix.target }}/release/${{ matrix.exe }}\n      - name: Zip Release\n        uses: TheDoctor0/zip-release@0.6.1\n        with:\n          type: zip\n          filename: rathole-${{ matrix.target }}.zip\n          directory: target/${{ matrix.target }}/release/\n          path: ${{ matrix.exe }}\n      - name: Publish\n        uses: softprops/action-gh-release@v1\n        if: startsWith(github.ref, 'refs/tags/')\n        with:\n          files: target/${{ matrix.target }}/release/rathole-${{ matrix.target }}.zip\n          generate_release_notes: true\n          draft: true\n  docker:\n    name: Publish to Docker Hub\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n    needs: release\n    steps:\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n      - name: Login to DockerHub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n      - name: Build and push\n        id: docker_build\n        uses: docker/build-push-action@v2\n        with:\n          push: true\n          platforms: linux/amd64, linux/arm64, linux/armhf, linux/armv7\n          tags: rapiz1/rathole:latest, rapiz1/rathole:${{ github.ref_name }}\n  publish-crate:\n    name: Publish to crates.io\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n    needs: release\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n      - name: Publish\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}\n        run: cargo publish\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non:\n  pull_request:\n    branches: [\"*\"]\n  push:\n    branches: [\"main\", \"dev\"]\n\nconcurrency:\n  # Documentation suggests ${{ github.head_ref }}, but that's only available on pull_request/pull_request_target triggers, so using ${{ github.ref }}.\n  # On main, we want all builds to complete even if merging happens faster to make it easier to discover at which point something broke.\n  group: ${{ github.ref == 'refs/heads/main' && format('ci-main-{0}', github.sha) || format('ci-{0}', github.ref) }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  lints:\n    name: Lints\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          components: clippy\n      - uses: Swatinem/rust-cache@v1\n      - name: Clippy\n        run: cargo clippy -- -D warnings\n      - name: Setup cargo-hack\n        run: cargo install cargo-hack\n      - name: Check all features\n        run: >\n          cargo hack check --feature-powerset --no-dev-deps\n          --mutually-exclusive-features default,native-tls,websocket-native-tls,rustls,websocket-rustls\n\n  build:\n    name: Build for ${{ matrix.target }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            exe: rathole\n            target: x86_64-unknown-linux-gnu\n          - os: windows-latest\n            exe: rathole.exe\n            target: x86_64-pc-windows-msvc\n          - os: macos-latest\n            exe: rathole\n            target: x86_64-apple-darwin\n          - os: macos-latest\n            exe: rathole\n            target: aarch64-apple-darwin\n\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n      - uses: Swatinem/rust-cache@v1\n      - name: Build\n        run: cargo build\n      - name: Run tests with native-tls\n        run: cargo test --verbose\n      - name: Run tests with rustls\n        run: cargo test --verbose --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload\n      - uses: actions/upload-artifact@v4\n        with:\n          name: rathole-${{ matrix.target }}\n          path: target/debug/${{ matrix.exe }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\nperf.data\nperf.data.old\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "imports_granularity = \"module\"\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"rathole\"\nversion = \"0.5.0\"\nedition = \"2021\"\nauthors = [\"Yujia Qiao <code@rapiz.me>\"]\ndescription = \"A reverse proxy for NAT traversal\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/rapiz1/rathole\"\nreadme = \"README.md\"\nbuild = \"build.rs\"\ninclude = [\"src/**/*\", \"LICENSE\", \"README.md\", \"build.rs\"]\n\n[features]\ndefault = [\n    \"server\",\n    \"client\",\n    \"native-tls\",\n    \"noise\",\n    \"websocket-native-tls\",\n    \"hot-reload\",\n]\n\n# Run as a server\nserver = []\n# Run as a client\nclient = []\n\n# TLS support\nnative-tls = [\"tokio-native-tls\"]\nrustls = [\n    \"tokio-rustls\",\n    \"rustls-pemfile\",\n    \"rustls-native-certs\",\n    \"p12\",\n]\n\n# Noise support\nnoise = [\"snowstorm\", \"base64\"]\n\n# Websocket support\nwebsocket-native-tls = [\n    \"tokio-tungstenite\",\n    \"tokio-util\",\n    \"futures-core\",\n    \"futures-sink\",\n    \"native-tls\",\n]\nwebsocket-rustls = [\n    \"tokio-tungstenite\",\n    \"tokio-util\",\n    \"futures-core\",\n    \"futures-sink\",\n    \"rustls\",\n]\n\n# Configuration hot-reload support\nhot-reload = [\"notify\"]\n\n# Default feature releasing embedded devices\n# Cross-compiling with tls is hard. So we don't :(\nembedded = [\"server\", \"client\", \"hot-reload\", \"noise\"]\n\n# Feature to enable tokio-console. Disabled by default.\n# Don't enable it unless for debugging purposes.\nconsole = [\"console-subscriber\", \"tokio/tracing\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n[profile.dev]\npanic = \"abort\"\n\n[profile.release]\npanic = \"abort\"\nlto = true\ncodegen-units = 1\nstrip = true\n\n[profile.bench]\ndebug = 1\n\n[profile.minimal]\ninherits = \"release\"\nopt-level = \"z\"\nlto = true\ncodegen-units = 1\n\n[dependencies]\ntokio = { version = \"1\", features = [\"full\"] }\nbytes = { version = \"1\", features = [\"serde\"] }\nclap = { version = \"3.0\", features = [\"derive\"] }\ntoml = \"0.5\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nanyhow = \"1.0\"\nsha2 = \"0.10\"\nbincode = \"1\"\nlazy_static = \"1.4\"\nhex = \"0.4\"\nrand = \"0.8\"\nbackoff = { version = \"0.4\", features = [\"tokio\"] }\ntracing = \"0.1\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\nsocket2 = { version = \"0.4\", features = [\"all\"] }\nfdlimit = \"0.2\"\nasync-trait = \"0.1\"\nsnowstorm = { version = \"0.4\", optional = true, features = [\n    \"stream\",\n], default-features = false }\nbase64 = { version = \"0.13\", optional = true }\nnotify = { version = \"5.0.0-pre.13\", optional = true }\nconsole-subscriber = { version = \"0.1\", optional = true, features = [\n    \"parking_lot\",\n] }\natty = \"0.2\"\nasync-http-proxy = { version = \"1.2\", features = [\n    \"runtime-tokio\",\n    \"basic-auth\",\n] }\nasync-socks5 = \"0.5\"\nurl = { version = \"2.2\", features = [\"serde\"] }\ntokio-tungstenite = { version = \"0.20.1\", optional = true }\ntokio-util = { version = \"0.7.9\", optional = true, features = [\"io\"] }\nfutures-core = { version = \"0.3.28\", optional = true }\nfutures-sink = { version = \"0.3.28\", optional = true }\ntokio-native-tls = { version = \"0.3\", optional = true }\ntokio-rustls = { version = \"0.25\", optional = true }\nrustls-native-certs = { version = \"0.7\", optional = true }\nrustls-pemfile = { version = \"2.0\", optional = true }\np12 = { version = \"0.6.3\", optional = true }\n\n[target.'cfg(target_env = \"musl\")'.dependencies]\nopenssl = { version = \"0.10\", features = [\"vendored\"], optional = true }\n\n[build-dependencies]\nvergen = { version = \"7.4.2\", default-features = false, features = [\n    \"build\",\n    \"git\",\n    \"cargo\",\n] }\nanyhow = \"1.0\"\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM rust:bookworm as builder\nRUN apt update && apt install -y libssl-dev\nWORKDIR /home/rust/src\nCOPY . .\nARG FEATURES\nRUN cargo build --locked --release --features ${FEATURES:-default}\nRUN mkdir -p build-out/\nRUN cp target/release/rathole build-out/\n\n\n\nFROM gcr.io/distroless/cc-debian12\nWORKDIR /app\nCOPY --from=builder /home/rust/src/build-out/rathole .\nUSER 1000:1000\nENTRYPOINT [\"./rathole\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README-zh.md",
    "content": "# rathole\n\n![rathole-logo](./docs/img/rathole-logo.png)\n\n[![GitHub stars](https://img.shields.io/github/stars/rapiz1/rathole)](https://github.com/rapiz1/rathole/stargazers)\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rapiz1/rathole)](https://github.com/rapiz1/rathole/releases)\n![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rapiz1/rathole/rust.yml?branch=main)\n[![GitHub all releases](https://img.shields.io/github/downloads/rapiz1/rathole/total)](https://github.com/rapiz1/rathole/releases)\n[![Docker Pulls](https://img.shields.io/docker/pulls/rapiz1/rathole)](https://hub.docker.com/r/rapiz1/rathole)\n\n[English](README.md) | [简体中文](README-zh.md)\n\n安全、稳定、高性能的内网穿透工具，用 Rust 语言编写\n\nrathole，类似于 [frp](https://github.com/fatedier/frp) 和 [ngrok](https://github.com/inconshreveable/ngrok)，可以让 NAT 后的设备上的服务通过具有公网 IP 的服务器暴露在公网上。\n\n<!-- TOC -->\n\n- [rathole](#rathole)\n  - [Features](#features)\n  - [Quickstart](#quickstart)\n  - [Configuration](#configuration)\n    - [Logging](#logging)\n    - [Tuning](#tuning)\n  - [Benchmark](#benchmark)\n  - [Development Status](#development-status)\n\n<!-- /TOC -->\n\n## Features\n\n- **高性能** 具有更高的吞吐量，高并发下更稳定。见[Benchmark](#benchmark)\n- **低资源消耗** 内存占用远低于同类工具。见[Benchmark](#benchmark)。[二进制文件最小](docs/build-guide.md)可以到 **~500KiB**，可以部署在嵌入式设备如路由器上。\n- **安全性** 每个服务单独强制鉴权。Server 和 Client 负责各自的配置。使用 Noise Protocol 可以简单地配置传输加密，而不需要自签证书。同时也支持 TLS。\n- **热重载** 支持配置文件热重载，动态修改端口转发服务。HTTP API 正在开发中。\n\n## Quickstart\n\n一个全功能的 `rathole` 可以从 [release](https://github.com/rapiz1/rathole/releases) 页面下载。或者 [从源码编译](docs/build-guide.md) **获取其他平台和最小化的二进制文件**。\n\n`rathole` 的使用和 frp 非常类似，如果你有后者的使用经验，那配置对你来说非常简单，区别只是转发服务的配置分离到了服务端和客户端，并且必须要设置 token。\n\n使用 rathole 需要一个有公网 IP 的服务器，和一个在 NAT 或防火墙后的设备，其中有些服务需要暴露在互联网上。\n\n假设你在家里的 NAT 后面有一个 NAS，并且想把它的 ssh 服务暴露在公网上：\n\n1. 在有一个公网 IP 的服务器上\n\n创建 `server.toml`，内容如下，并根据你的需要调整。\n\n```toml\n# server.toml\n[server]\nbind_addr = \"0.0.0.0:2333\" # `2333` 配置了服务端监听客户端连接的端口\n\n[server.services.my_nas_ssh]\ntoken = \"use_a_secret_that_only_you_know\" # 用于验证的 token\nbind_addr = \"0.0.0.0:5202\" # `5202` 配置了将 `my_nas_ssh` 暴露给互联网的端口\n```\n\n然后运行:\n\n```bash\n./rathole server.toml\n```\n\n2. 在 NAT 后面的主机（你的 NAS）上\n\n创建 `client.toml`，内容如下，并根据你的需要进行调整。\n\n```toml\n# client.toml\n[client]\nremote_addr = \"myserver.com:2333\" # 服务器的地址。端口必须与 `server.bind_addr` 中的端口相同。\n[client.services.my_nas_ssh]\ntoken = \"use_a_secret_that_only_you_know\" # 必须与服务器相同以通过验证\nlocal_addr = \"127.0.0.1:22\" # 需要被转发的服务的地址\n```\n\n然后运行：\n\n```bash\n./rathole client.toml\n```\n\n3. 现在 `rathole` 客户端会连接运行在 `myserver.com:2333`的 `rathole` 服务器，任何到 `myserver.com:5202` 的流量将被转发到客户端所在主机的 `22` 端口。\n\n所以你可以 `ssh myserver.com:5202` 来 ssh 到你的 NAS。\n\n[Systemd examples](./examples/systemd) 中提供了一些让 `rathole` 在 Linux 上作为后台服务运行的配置示例。\n\n## Configuration\n\n如果只有一个 `[server]` 和 `[client]` 块存在的话，`rathole` 可以根据配置文件的内容自动决定在服务器模式或客户端模式下运行，就像 [Quickstart](#quickstart) 中的例子。\n\n但 `[client]` 和 `[server]` 块也可以放在一个文件中。然后在服务器端，运行 `rathole --server config.toml`。在客户端，运行 `rathole --client config.toml` 来明确告诉 `rathole` 运行模式。\n\n**推荐首先查看 [examples](./examples) 中的配置示例来快速理解配置格式**，如果有不清楚的地方再查阅完整配置格式。\n\n关于如何配置 Noise Protocol 和 TLS 来进行加密传输，参见 [Transport](./docs/transport.md)。\n\n下面是完整的配置格式。\n\n```toml\n[client]\nremote_addr = \"example.com:2333\" # Necessary. The address of the server\ndefault_token = \"default_token_if_not_specify\" # Optional. The default token of services, if they don't define their own ones\nheartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds\nretry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second\n\n[client.transport] # The whole block is optional. Specify which transport to use\ntype = \"tcp\" # Optional. Possible values: [\"tcp\", \"tls\", \"noise\"]. Default: \"tcp\"\n\n[client.transport.tcp] # Optional. Also affects `noise` and `tls`\nproxy = \"socks5://user:passwd@127.0.0.1:1080\" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported.\nnodelay = true # Optional. Override the `client.transport.nodelay` per service\nkeepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds\nkeepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds\n\n[client.transport.tls] # Necessary if `type` is \"tls\"\ntrusted_root = \"ca.pem\" # Necessary. The certificate of CA that signed the server's certificate\nhostname = \"example.com\" # Optional. The hostname that the client uses to validate the certificate. If not set, fallback to `client.remote_addr`\n\n[client.transport.noise] # Noise protocol. See `docs/transport.md` for further explanation\npattern = \"Noise_NK_25519_ChaChaPoly_BLAKE2s\" # Optional. Default value as shown\nlocal_private_key = \"key_encoded_in_base64\" # Optional\nremote_public_key = \"key_encoded_in_base64\" # Optional\n\n[client.transport.websocket] # Necessary if `type` is \"websocket\"\ntls = true # If `true` then it will use settings in `client.transport.tls`\n\n[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration\ntype = \"tcp\" # Optional. The protocol that needs forwarding. Possible values: [\"tcp\", \"udp\"]. Default: \"tcp\"\ntoken = \"whatever\" # Necessary if `client.default_token` not set\nlocal_addr = \"127.0.0.1:1081\" # Necessary. The address of the service that needs to be forwarded\nnodelay = true # Optional. Determine whether to enable TCP_NODELAY for data transmission, if applicable, to improve the latency but decrease the bandwidth. Default: true\nretry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config\n\n[client.services.service2] # Multiple services can be defined\nlocal_addr = \"127.0.0.1:1082\"\n\n[server]\nbind_addr = \"0.0.0.0:2333\" # Necessary. The address that the server listens for clients. Generally only the port needs to be change.\ndefault_token = \"default_token_if_not_specify\" # Optional\nheartbeat_interval = 30 # Optional. The interval between two application-layer heartbeat. Set to 0 to disable sending heartbeat. Default: 30 seconds\n\n[server.transport] # Same as `[client.transport]`\ntype = \"tcp\"\n\n[server.transport.tcp] # Same as the client\nnodelay = true\nkeepalive_secs = 20\nkeepalive_interval = 8\n\n[server.transport.tls] # Necessary if `type` is \"tls\"\npkcs12 = \"identify.pfx\" # Necessary. pkcs12 file of server's certificate and private key\npkcs12_password = \"password\" # Necessary. Password of the pkcs12 file\n\n[server.transport.noise] # Same as `[client.transport.noise]`\npattern = \"Noise_NK_25519_ChaChaPoly_BLAKE2s\"\nlocal_private_key = \"key_encoded_in_base64\"\nremote_public_key = \"key_encoded_in_base64\"\n\n[server.transport.websocket] # Necessary if `type` is \"websocket\"\ntls = true # If `true` then it will use settings in `server.transport.tls`\n\n[server.services.service1] # The service name must be identical to the client side\ntype = \"tcp\" # Optional. Same as the client `[client.services.X.type]\ntoken = \"whatever\" # Necessary if `server.default_token` not set\nbind_addr = \"0.0.0.0:8081\" # Necessary. The address of the service is exposed at. Generally only the port needs to be change.\nnodelay = true # Optional. Same as the client\n\n[server.services.service2]\nbind_addr = \"0.0.0.1:8082\"\n```\n\n### Logging\n\n`rathole`，像许多其他 Rust 程序一样，使用环境变量来控制日志级别。\n\n支持的 Logging Level 有 `info`, `warn`, `error`, `debug`, `trace`\n\n比如将日志级别设置为 `error`:\n\n```shell\nRUST_LOG=error ./rathole config.toml\n```\n\n如果 `RUST_LOG` 不存在，默认的日志级别是 `info`。\n\n### Tuning\n\n从 v0.4.7 开始, rathole 默认启用 TCP_NODELAY。这能够减少延迟并使交互式应用受益，比如 RDP，Minecraft 服务器。但它会减少一些带宽。\n\n如果带宽更重要，比如网盘类应用，TCP_NODELAY 仍然可以通过配置 `nodelay = false` 关闭。\n\n## Benchmark\n\nrathole 的延迟与 [frp](https://github.com/fatedier/frp) 相近，在高并发情况下表现更好，能提供更大的带宽，内存占用更少。\n\n关于测试进行的更多细节，参见单独页面 [Benchmark](./docs/benchmark.md)。\n\n**但是，不要从这里得出结论，`rathole` 能让内网转发出来的服务快上数倍。** Benchmark 是在本地回环上进行的，其结果说明了任务受 CPU 限制时的结果。当用户的网络不是瓶颈时，用户能得到很大的提升。但是，对很多用户来说并不是这样。在这种情况下，`rathole` 能带来的主要好处是更少的资源占用，而带宽和延迟不一定有显著的改善。\n\n![http_throughput](./docs/img/http_throughput.svg)\n![tcp_bitrate](./docs/img/tcp_bitrate.svg)\n![udp_bitrate](./docs/img/udp_bitrate.svg)\n![mem](./docs/img/mem-graph.png)\n\n## Development Status\n\n`rathole` 正在积极开发中\n\n- [x] 支持 TLS\n- [x] 支持 UDP\n- [x] 热重载\n- [ ] 用于配置的 HTTP APIs\n\n[Out of Scope](./docs/out-of-scope.md) 列举了没有计划开发的特性并说明了原因。\n"
  },
  {
    "path": "README.md",
    "content": "# rathole\n\n![rathole-logo](./docs/img/rathole-logo.png)\n\n[![GitHub stars](https://img.shields.io/github/stars/rapiz1/rathole)](https://github.com/rapiz1/rathole/stargazers)\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rapiz1/rathole)](https://github.com/rapiz1/rathole/releases)\n![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rapiz1/rathole/rust.yml?branch=main)\n[![GitHub all releases](https://img.shields.io/github/downloads/rapiz1/rathole/total)](https://github.com/rapiz1/rathole/releases)\n[![Docker Pulls](https://img.shields.io/docker/pulls/rapiz1/rathole)](https://hub.docker.com/r/rapiz1/rathole)\n[![Join the chat at https://gitter.im/rapiz1/rathole](https://badges.gitter.im/rapiz1/rathole.svg)](https://gitter.im/rapiz1/rathole?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\n[English](README.md) | [简体中文](README-zh.md)\n\nA secure, stable and high-performance reverse proxy for NAT traversal, written in Rust\n\nrathole, like [frp](https://github.com/fatedier/frp) and [ngrok](https://github.com/inconshreveable/ngrok), can help to expose the service on the device behind the NAT to the Internet, via a server with a public IP.\n\n<!-- TOC -->\n\n- [rathole](#rathole)\n  - [Features](#features)\n  - [Quickstart](#quickstart)\n  - [Configuration](#configuration)\n    - [Logging](#logging)\n    - [Tuning](#tuning)\n  - [Benchmark](#benchmark)\n  - [Planning](#planning)\n\n<!-- /TOC -->\n\n## Features\n\n- **High Performance** Much higher throughput can be achieved than frp, and more stable when handling a large volume of connections. See [Benchmark](#benchmark)\n- **Low Resource Consumption** Consumes much fewer memory than similar tools. See [Benchmark](#benchmark). [The binary can be](docs/build-guide.md) **as small as ~500KiB** to fit the constraints of devices, like embedded devices as routers.\n- **Security** Tokens of services are mandatory and service-wise. The server and clients are responsible for their own configs. With the optional Noise Protocol, encryption can be configured at ease. No need to create a self-signed certificate! TLS is also supported.\n- **Hot Reload** Services can be added or removed dynamically by hot-reloading the configuration file. HTTP API is WIP.\n\n## Quickstart\n\nA full-powered `rathole` can be obtained from the [release](https://github.com/rapiz1/rathole/releases) page. Or [build from source](docs/build-guide.md) **for other platforms and minimizing the binary**. A [Docker image](https://hub.docker.com/r/rapiz1/rathole) is also available.\n\nThe usage of `rathole` is very similar to frp. If you have experience with the latter, then the configuration is very easy for you. The only difference is that configuration of a service is split into the client side and the server side, and a token is mandatory.\n\nTo use `rathole`, you need a server with a public IP, and a device behind the NAT, where some services that need to be exposed to the Internet.\n\nAssuming you have a NAS at home behind the NAT, and want to expose its ssh service to the Internet:\n\n1. On the server which has a public IP\n\nCreate `server.toml` with the following content and accommodate it to your needs.\n\n```toml\n# server.toml\n[server]\nbind_addr = \"0.0.0.0:2333\" # `2333` specifies the port that rathole listens for clients\n\n[server.services.my_nas_ssh]\ntoken = \"use_a_secret_that_only_you_know\" # Token that is used to authenticate the client for the service. Change to an arbitrary value.\nbind_addr = \"0.0.0.0:5202\" # `5202` specifies the port that exposes `my_nas_ssh` to the Internet\n```\n\nThen run:\n\n```bash\n./rathole server.toml\n```\n\n2. On the host which is behind the NAT (your NAS)\n\nCreate `client.toml` with the following content and accommodate it to your needs.\n\n```toml\n# client.toml\n[client]\nremote_addr = \"myserver.com:2333\" # The address of the server. The port must be the same with the port in `server.bind_addr`\n\n[client.services.my_nas_ssh]\ntoken = \"use_a_secret_that_only_you_know\" # Must be the same with the server to pass the validation\nlocal_addr = \"127.0.0.1:22\" # The address of the service that needs to be forwarded\n```\n\nThen run:\n\n```bash\n./rathole client.toml\n```\n\n3. Now the client will try to connect to the server `myserver.com` on port `2333`, and any traffic to `myserver.com:5202` will be forwarded to the client's port `22`.\n\nSo you can `ssh myserver.com:5202` to ssh to your NAS.\n\nTo run `rathole` run as a background service on Linux, checkout the [systemd examples](./examples/systemd).\n\n## Configuration\n\n`rathole` can automatically determine to run in the server mode or the client mode, according to the content of the configuration file, if only one of `[server]` and `[client]` block is present, like the example in [Quickstart](#quickstart).\n\nBut the `[client]` and `[server]` block can also be put in one file. Then on the server side, run `rathole --server config.toml` and on the client side, run `rathole --client config.toml` to explicitly tell `rathole` the running mode.\n\nBefore heading to the full configuration specification, it's recommend to skim [the configuration examples](./examples) to get a feeling of the configuration format.\n\nSee [Transport](./docs/transport.md) for more details about encryption and the `transport` block.\n\nHere is the full configuration specification:\n\n```toml\n[client]\nremote_addr = \"example.com:2333\" # Necessary. The address of the server\ndefault_token = \"default_token_if_not_specify\" # Optional. The default token of services, if they don't define their own ones\nheartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds\nretry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second\n\n[client.transport] # The whole block is optional. Specify which transport to use\ntype = \"tcp\" # Optional. Possible values: [\"tcp\", \"tls\", \"noise\"]. Default: \"tcp\"\n\n[client.transport.tcp] # Optional. Also affects `noise` and `tls`\nproxy = \"socks5://user:passwd@127.0.0.1:1080\" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported.\nnodelay = true # Optional. Determine whether to enable TCP_NODELAY, if applicable, to improve the latency but decrease the bandwidth. Default: true\nkeepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds\nkeepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds\n\n[client.transport.tls] # Necessary if `type` is \"tls\"\ntrusted_root = \"ca.pem\" # Necessary. The certificate of CA that signed the server's certificate\nhostname = \"example.com\" # Optional. The hostname that the client uses to validate the certificate. If not set, fallback to `client.remote_addr`\n\n[client.transport.noise] # Noise protocol. See `docs/transport.md` for further explanation\npattern = \"Noise_NK_25519_ChaChaPoly_BLAKE2s\" # Optional. Default value as shown\nlocal_private_key = \"key_encoded_in_base64\" # Optional\nremote_public_key = \"key_encoded_in_base64\" # Optional\n\n[client.transport.websocket] # Necessary if `type` is \"websocket\"\ntls = true # If `true` then it will use settings in `client.transport.tls`\n\n[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration\ntype = \"tcp\" # Optional. The protocol that needs forwarding. Possible values: [\"tcp\", \"udp\"]. Default: \"tcp\"\ntoken = \"whatever\" # Necessary if `client.default_token` not set\nlocal_addr = \"127.0.0.1:1081\" # Necessary. The address of the service that needs to be forwarded\nnodelay = true # Optional. Override the `client.transport.nodelay` per service\nretry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config\n\n[client.services.service2] # Multiple services can be defined\nlocal_addr = \"127.0.0.1:1082\"\n\n[server]\nbind_addr = \"0.0.0.0:2333\" # Necessary. The address that the server listens for clients. Generally only the port needs to be change.\ndefault_token = \"default_token_if_not_specify\" # Optional\nheartbeat_interval = 30 # Optional. The interval between two application-layer heartbeat. Set to 0 to disable sending heartbeat. Default: 30 seconds\n\n[server.transport] # Same as `[client.transport]`\ntype = \"tcp\"\n\n[server.transport.tcp] # Same as the client\nnodelay = true\nkeepalive_secs = 20\nkeepalive_interval = 8\n\n[server.transport.tls] # Necessary if `type` is \"tls\"\npkcs12 = \"identify.pfx\" # Necessary. pkcs12 file of server's certificate and private key\npkcs12_password = \"password\" # Necessary. Password of the pkcs12 file\n\n[server.transport.noise] # Same as `[client.transport.noise]`\npattern = \"Noise_NK_25519_ChaChaPoly_BLAKE2s\"\nlocal_private_key = \"key_encoded_in_base64\"\nremote_public_key = \"key_encoded_in_base64\"\n\n[server.transport.websocket] # Necessary if `type` is \"websocket\"\ntls = true # If `true` then it will use settings in `server.transport.tls`\n\n[server.services.service1] # The service name must be identical to the client side\ntype = \"tcp\" # Optional. Same as the client `[client.services.X.type]\ntoken = \"whatever\" # Necessary if `server.default_token` not set\nbind_addr = \"0.0.0.0:8081\" # Necessary. The address of the service is exposed at. Generally only the port needs to be change.\nnodelay = true # Optional. Same as the client\n\n[server.services.service2]\nbind_addr = \"0.0.0.1:8082\"\n```\n\n### Logging\n\n`rathole`, like many other Rust programs, use environment variables to control the logging level. `info`, `warn`, `error`, `debug`, `trace` are available.\n\n```shell\nRUST_LOG=error ./rathole config.toml\n```\n\nwill run `rathole` with only error level logging.\n\nIf `RUST_LOG` is not present, the default logging level is `info`.\n\n### Tuning\n\nFrom v0.4.7, rathole enables TCP_NODELAY by default, which should benefit the latency and interactive applications like rdp, Minecraft servers. However, it slightly decreases the bandwidth.\n\nIf the bandwidth is more important, TCP_NODELAY can be opted out with `nodelay = false`.\n\n## Benchmark\n\nrathole has similar latency to [frp](https://github.com/fatedier/frp), but can handle a more connections, provide larger bandwidth, with less memory usage.\n\nFor more details, see the separate page [Benchmark](./docs/benchmark.md).\n\n**However, don't take it from here that `rathole` can magically make your forwarded service faster several times than before.** The benchmark is done on local loopback, indicating the performance when the task is cpu-bounded. One can gain quite a improvement if the network is not the bottleneck. Unfortunately, that's not true for many users. In that case, the main benefit is lower resource consumption, while the bandwidth and the latency may not improved significantly.\n\n![http_throughput](./docs/img/http_throughput.svg)\n![tcp_bitrate](./docs/img/tcp_bitrate.svg)\n![udp_bitrate](./docs/img/udp_bitrate.svg)\n![mem](./docs/img/mem-graph.png)\n\n## Planning\n\n- [ ] HTTP APIs for configuration\n\n[Out of Scope](./docs/out-of-scope.md) lists features that are not planned to be implemented and why.\n"
  },
  {
    "path": "benches/scripts/http/latency.sh",
    "content": "#!/bin/sh\nRATE=\"1 1000 2000 3000 4000\"\nDURATION=\"60s\"\n\nRATHOLE=\"http://127.0.0.1:5202\"\nFRP=\"http://127.0.0.1:5203\"\n\necho warming up frp\necho GET $FRP | vegeta attack -duration 10s > /dev/null\nfor rate in $RATE; do\n        name=\"frp-${rate}qps-$DURATION.bin\"\n        echo $name\n        echo GET $FRP | vegeta attack -rate $rate -duration $DURATION > $name\n        vegeta report $name\ndone\n\necho warming up rathole\necho GET $RATHOLE | vegeta attack -duration 10s > /dev/null\nfor rate in $RATE; do\n        name=\"rathole-${rate}qps-$DURATION.bin\"\n        echo $name\n        echo GET $RATHOLE | vegeta attack -rate $rate -duration $DURATION > $name\n        vegeta report $name\ndone\n"
  },
  {
    "path": "benches/scripts/mem/mem.sh",
    "content": "#!/bin/bash\n\nrm -v *-mem.log\n\necho frp\nwhile true; do\n\tps -C frpc -o rsz= >> frpc-mem.log\nsleep 1\ndone &\n\nwhile true; do\n\tps -C frps -o rsz= >> frps-mem.log\nsleep 1\ndone &\n\necho GET http://127.0.0.1:5203 | vegeta attack -duration 30s -rate 1000  > /dev/null\n\nsleep 10\n\nkill $(jobs -p)\n\n\necho rathole\n\npid_s=$(ps aux | grep \"rathole -s\" | head -n 1 | awk '{print $2}')\nwhile true; do\n\tps --pid $pid_s -o rsz= >> ratholec-mem.log\nsleep 1\ndone &\n\npid_c=$(ps aux | grep \"rathole -c\" | head -n 1 | awk '{print $2}')\nwhile true; do\n\tps --pid $pid_c -o rsz= >> ratholes-mem.log\nsleep 1\ndone &\n\necho GET http://127.0.0.1:5202 | vegeta attack -duration 30s -rate 1000 > /dev/null\n\nsleep 10\n\nkill $(jobs -p)\n\ngawk -i inplace '{print $1 \"000\"}' frpc-mem.log\ngawk -i inplace '{print $1 \"000\"}' frps-mem.log\ngawk -i inplace '{print $1 \"000\"}' ratholec-mem.log\ngawk -i inplace '{print $1 \"000\"}' ratholes-mem.log\n"
  },
  {
    "path": "benches/scripts/mem/plot.plt",
    "content": "set title \"Memory Usage\" font \",20\"\n\nset term png small size 800,600\nset key box outside\n\nset output \"mem-graph.png\"\n\nset ylabel \"RSZ\"\nset format y '%.0s%cB'\n\nset ytics nomirror\n\nset yrange [0:*]\n\nplot \"frps-mem.log\" using 1 with lines axes x1y1 title \"frps RSZ\", \\\n     \"frpc-mem.log\" using 1 with lines axes x1y1 title \"frpc RSZ\", \\\n     \"ratholes-mem.log\" using 1 with lines axes x1y1 title \"ratholes RSZ\", \\\n     \"ratholec-mem.log\" using 1 with lines axes x1y1 title \"ratholec RSZ\"\n"
  },
  {
    "path": "build.rs",
    "content": "use anyhow::Result;\nuse vergen::{vergen, Config, SemverKind};\n\nfn main() -> Result<()> {\n    let mut config = Config::default();\n    // Change the SEMVER output to the lightweight variant\n    *config.git_mut().semver_kind_mut() = SemverKind::Lightweight;\n    // Add a `-dirty` flag to the SEMVER output\n    *config.git_mut().semver_dirty_mut() = Some(\"-dirty\");\n    // Generate the instructions\n    if let Err(e) = vergen(config) {\n        eprintln!(\"error occurred while generating instructions: {:?}\", e);\n        let mut config = Config::default();\n        *config.git_mut().enabled_mut() = false;\n        vergen(config)\n    } else {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "docs/benchmark.md",
    "content": "# Benchmark\n\n> Date: 2021/12/28\n>\n> Version: commit 1180c7e538564efd69742f22e77453a1b74a5ed2\n> \n> Arch Linux with 5.15.11-arch2-1 kernel\n>\n> Intel Xeon CPU E5-2620 @ 2.00GHz *2\n>\n> 16GB RAM\n\n## Bandwidth\n\n![tcp_bitrate](./img/tcp_bitrate.svg)\n![udp_bitrate](./img/udp_bitrate.svg)\n\nrathole with the following configuration:\n```toml\n[client]\nremote_addr = \"localhost:2333\"\ndefault_token = \"123\"\n\n[client.services.bench-tcp]\nlocal_addr = \"127.0.0.1:80\"\n[client.services.bench-udp]\ntype = \"udp\"\nlocal_addr = \"127.0.0.1:80\"\n\n[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.services.bench-tcp]\nbind_addr = \"0.0.0.0:5202\"\n[server.services.bench-udp]\ntype = \"udp\"\nbind_addr = \"0.0.0.0:5202\"\n```\n\nfrp 0.38.0 with the following configuration:\n```ini\n[common]\nbind_port = 7000\nauthentication_method = token\ntoken = 1233\n```\n```ini\n# frpc.ini\n[common]\nserver_addr = 127.0.0.1\nserver_port = 7000\nauthentication_method = token\ntoken = 1233\n\n[bench-tcp]\ntype = tcp\nlocal_ip = 127.0.0.1\nlocal_port = 80\nremote_port = 5203\n[bench-udp]\ntype = udp\nlocal_ip = 127.0.0.1\nlocal_port = 80\nremote_port = 5203\n```\n\n```\n$ iperf3 -v\niperf 3.10.1 (cJSON 1.7.13)\nLinux sig 5.15.7-arch1-1 #1 SMP PREEMPT Wed, 08 Dec 2021 14:33:16 +0000 x86_64\nOptional features available: CPU affinity setting, IPv6 flow label, TCP congestion algorithm setting, sendfile / zerocopy, socket pacing, authentication, bind to device, support IPv4 don't fragment\n$ sudo iperf3 -s -p 80\n```\n\nFor rathole benchmark:\n```\n$ iperf3 -c 127.0.0.1 -p 5202\n```\n\nFor frp benchmark:\n```\n$ iperf3 -c 127.0.0.1 -p 5203\n```\n\n## HTTP\n\nnginx/1.20.2 listens on port 80, with the default test page.\n\nfrp and rathole configuration is same with the previous section.\n\n[vegeta](https://github.com/tsenart/vegeta) is used to generate HTTP load.\n\n### HTTP Throughput\n\nThe following commands are used to benchmark rathole and frp. Note that if you want to do a benchmark yourself, `-max-workers` should be adjusted to get the accurate results for your machine.\n\n```\necho 'GET http://127.0.0.1:5203' | vegeta attack -rate 0 -duration 30s -max-workers 48\necho 'GET http://127.0.0.1:5202' | vegeta attack -rate 0 -duration 30s -max-workers 48\n```\n\n![http_throughput](./img/http_throughput.svg)\n\n### HTTP Latency\n\n`rathole` has very similar latency to `frp`, but can handle more connections\n\nHere's a table, latency is in ms\n\n|QPS|latency(rathole)|latency(frp)|\n|--|--|---|\n|1|2.113|2.55|\n|1000|1.723|1.742|\n|2000|1.845|1.749|\n|3000|2.064|2.011|\n|4000|2.569|7907|\n\nAs you can see, for QPS from 1 to 3000, rathole and frp have nearly identical latency.\nBut with QPS of 4000, frp starts reporting lots of errors and the latency grows to even seconds. This kind of reflects the throughput in the previous section.\n\nThus, in terms of latency, rathole and frp are nearly the same. But rathole can handle more connections.\n\n[Script to benchmark latency](../benches/scripts/http/latency.sh)\n\n## Memory Usage\n\n![mem](./img/mem-graph.png)\n\nThe graph shows the memory usage of frp and rathole when `vegeta attack -duration 30s -rate 1000` is executed.\nrathole uses much less memory than frp.\n\n[Script to benchmark memory](../benches/scripts/mem/mem.sh)\n"
  },
  {
    "path": "docs/build-guide.md",
    "content": "# Build Guide\n\nThis is for those who want to build `rathole` themselves, possibly because the need of latest features or the minimal binary size.\n\n## Build\n\nTo use default build settings, run:\n\n```sh\ncargo build --release\n```\n\nYou may need to pre-install [openssl](https://docs.rs/openssl/latest/openssl/index.html) dependencies in Unix-like systems.\n\n## Customize the Build\n\n`rathole` comes with lots of *crate features* that determine whether a certain feature will be compiled or not. Supported features can be checked out in `[features]` of [Cargo.toml](../Cargo.toml).\n\nFor example, to build `rathole` with the `client` and `noise` feature:\n\n```sh\ncargo build --release --no-default-features --features client,noise\n```\n\n## Rustls Support\n\n`rathole` provides optional `rustls` support. It's an almost drop-in replacement of `native-tls` support. (See [Transport](transport.md) for more information.)\n\nTo enable this, disable the default features and enable `rustls` feature. And for websocket feature, enable `websocket-rustls` feature as well.\n\nYou can also use command line option for this. For example, to replace all default features with `rustls`:\n\n```sh\ncargo build --release --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload\n```\n\nFeature `rustls` and `websocket-rustls` cannot be enabled with `native-tls` and `websocket-native-tls` at the same time, as they are mutually exclusive. Enabling both will result in a compile error.\n\n(Note that default features contains `native-tls` and `websocket-native-tls`.)\n\n## Minimalize the binary\n\n1. Build with the `minimal` profile\n\nThe `release` build profile optimize for the program running time, not the binary size.\n\nHowever, the `minimal` profile enables lots of optimization for the binary size to produce a much smaller binary.\n\nFor example, to build `rathole` with `client` feature with the `minimal` profile:\n\n```sh\ncargo build --profile minimal --no-default-features --features client\n```\n\n2. `strip` and `upx`\n\nThe binary that step 1 produces can be even smaller, by using `strip` and `upx` to remove the symbols and compress the binary.\n\nLike:\n\n```sh\nstrip rathole\nupx --best --lzma rathole\n```\n\nAt the time of writting the build guide, the produced binary for `x86_64-unknown-linux-glibc` has the size of **574 KiB**, while `frpc` has the size of **~10 MiB**, which is much larger.\n"
  },
  {
    "path": "docs/img/overview.excalidraw",
    "content": "{\n  \"type\": \"excalidraw\",\n  \"version\": 2,\n  \"source\": \"https://excalidraw.com\",\n  \"elements\": [\n    {\n      \"type\": \"rectangle\",\n      \"version\": 127,\n      \"versionNonce\": 80643966,\n      \"isDeleted\": false,\n      \"id\": \"_ROJe0KCjbnKQjLDcc-Ag\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 274.66668701171875,\n      \"y\": 87.49995422363281,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 450.66668701171875,\n      \"height\": 208.66667175292972,\n      \"seed\": 1939336259,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"boundElementIds\": [\n        \"1Sorez2zxxKqyRilx21-m\",\n        \"uJx77oj5eyZPw61wszaJN\"\n      ],\n      \"updated\": 1639393963541\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 248,\n      \"versionNonce\": 1524512610,\n      \"isDeleted\": false,\n      \"id\": \"X-BwNQGYSBy-tPINiKBCt\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 283.33331298828125,\n      \"y\": 94.50007629394531,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 61,\n      \"height\": 25,\n      \"seed\": 429932333,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963541,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"Server\",\n      \"baseline\": 18,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\"\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 166,\n      \"versionNonce\": 1926031294,\n      \"isDeleted\": false,\n      \"id\": \"5KLQ8EXnY3KjzuLRGbhJU\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 873.3333129882812,\n      \"y\": 151.5,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 37.33331298828125,\n      \"height\": 34.66667175292969,\n      \"seed\": 565619875,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"boundElementIds\": [\n        \"-lU_z4mfDB58ZiJ8HlTxY\"\n      ],\n      \"updated\": 1639393963541\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 112,\n      \"versionNonce\": 447019810,\n      \"isDeleted\": false,\n      \"id\": \"ViC_qO7r1ED1cN1IlPe7s\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 892.6666259765625,\n      \"y\": 188.16668701171875,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 0,\n      \"height\": 34,\n      \"seed\": 1032403459,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963541,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          34\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 86,\n      \"versionNonce\": 177553406,\n      \"isDeleted\": false,\n      \"id\": \"8SCEaNme89qCxY-xAS0it\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 890,\n      \"y\": 199.5,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 24,\n      \"height\": 18.66668701171875,\n      \"seed\": 773580109,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963541,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -24,\n          18.66668701171875\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 110,\n      \"versionNonce\": 73221858,\n      \"isDeleted\": false,\n      \"id\": \"Kbl62J0jyfWlbTEVgMqJH\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 895.3333129882812,\n      \"y\": 197.5,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 24,\n      \"height\": 18,\n      \"seed\": 464452045,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963541,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          24,\n          18\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 130,\n      \"versionNonce\": 1881706558,\n      \"isDeleted\": false,\n      \"id\": \"PRHAdurETJSYCaa5l6Iwa\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 892,\n      \"y\": 222.16668701171875,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 14.66668701171875,\n      \"height\": 25.33331298828125,\n      \"seed\": 1595489411,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963542,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -14.66668701171875,\n          25.33331298828125\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"version\": 162,\n      \"versionNonce\": 1888885410,\n      \"isDeleted\": false,\n      \"id\": \"HkWBRTjPa-LTKYPbw8XS-\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 894,\n      \"y\": 223.5,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 17.630663207545922,\n      \"height\": 23.561635782942176,\n      \"seed\": 1412110733,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963542,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": null,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          17.630663207545922,\n          23.561635782942176\n        ]\n      ]\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 307,\n      \"versionNonce\": 1975983586,\n      \"isDeleted\": false,\n      \"id\": \"2GauISsAXxBxXaARLdO2v\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 277.6666564941406,\n      \"y\": 419.99999237060547,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 442,\n      \"height\": 132.0000152587891,\n      \"seed\": 1008142253,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"boundElementIds\": [],\n      \"updated\": 1639394067925\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 375,\n      \"versionNonce\": 69970238,\n      \"isDeleted\": false,\n      \"id\": \"4t6IqDCz_2ovUHEWf3VyP\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 287.16668701171875,\n      \"y\": 428.4999084472656,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 54,\n      \"height\": 25,\n      \"seed\": 1136307299,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"boundElementIds\": [\n        \"uJx77oj5eyZPw61wszaJN\"\n      ],\n      \"updated\": 1639394067925,\n      \"fontSize\": 20,\n      \"fontFamily\": 1,\n      \"text\": \"Client\",\n      \"baseline\": 18,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\"\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 406,\n      \"versionNonce\": 1298876478,\n      \"isDeleted\": false,\n      \"id\": \"i-iOOSRyBhiISzIY5AG2O\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 650,\n      \"y\": 135.50001525878906,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 72,\n      \"height\": 40,\n      \"seed\": 1004543373,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [\n        \"1Sorez2zxxKqyRilx21-m\"\n      ],\n      \"updated\": 1639394028864,\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"text\": \"service1\\nbind addr\",\n      \"baseline\": 34,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\"\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 471,\n      \"versionNonce\": 437130622,\n      \"isDeleted\": false,\n      \"id\": \"Lld8m5f8AeGoMRmkfryGK\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 650,\n      \"y\": 246.1667022705078,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 72,\n      \"height\": 40,\n      \"seed\": 1760597182,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [\n        \"1Sorez2zxxKqyRilx21-m\"\n      ],\n      \"updated\": 1639394090709,\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"text\": \"service2\\nbind addr\",\n      \"baseline\": 34,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\"\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 212,\n      \"versionNonce\": 1840891362,\n      \"isDeleted\": false,\n      \"id\": \"5io-dv6h3U5ORt9DXFq37\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 308.66668701171875,\n      \"y\": 250.83334350585938,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 72,\n      \"height\": 40,\n      \"seed\": 1771953379,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [\n        \"wLQ-nby5mNnwfX9LnFrEt\"\n      ],\n      \"updated\": 1639393963542,\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"text\": \"server\\nbind addr\",\n      \"baseline\": 34,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"top\"\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 228,\n      \"versionNonce\": 1728857058,\n      \"isDeleted\": false,\n      \"id\": \"-lU_z4mfDB58ZiJ8HlTxY\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 870.7065228655306,\n      \"y\": 194.87651239705974,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 129.96929122242796,\n      \"height\": 1.4508687081649327,\n      \"seed\": 585847683,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"boundElementIds\": [],\n      \"updated\": 1639393963543,\n      \"startBinding\": {\n        \"elementId\": \"5KLQ8EXnY3KjzuLRGbhJU\",\n        \"focus\": -1.5107931785090518,\n        \"gap\": 15.791401051287782\n      },\n      \"endBinding\": {\n        \"elementId\": \"mR2qjxJFdOso9NGgCoq4h\",\n        \"focus\": 0.3208591338543321,\n        \"gap\": 10.737231643102632\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -129.96929122242796,\n          -1.4508687081649327\n        ]\n      ]\n    },\n    {\n      \"id\": \"2DQbzxVigt_dM1muvXWYN\",\n      \"type\": \"text\",\n      \"x\": 872.8333740234375,\n      \"y\": 127,\n      \"width\": 49,\n      \"height\": 20,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 1110579390,\n      \"version\": 21,\n      \"versionNonce\": 974179198,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639393963543,\n      \"text\": \"visitor\",\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 14\n    },\n    {\n      \"id\": \"LU4D6A2Ugd1V9uKE6SwOC\",\n      \"type\": \"arrow\",\n      \"x\": 696.2380319060499,\n      \"y\": 189.44727288880773,\n      \"width\": 134.59847736695497,\n      \"height\": 6.61671213194353,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 275565346,\n      \"version\": 351,\n      \"versionNonce\": 481916706,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639393963543,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -134.59847736695497,\n          6.61671213194353\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"mR2qjxJFdOso9NGgCoq4h\",\n        \"focus\": 0.0597242207313621,\n        \"gap\": 9.095342117387645\n      },\n      \"endBinding\": {\n        \"elementId\": \"NzpaVP1cgsvfg6KfdD99G\",\n        \"focus\": 0.1507396149689704,\n        \"gap\": 3.4879322512409843\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"NzpaVP1cgsvfg6KfdD99G\",\n      \"type\": \"diamond\",\n      \"x\": 372.66668701171875,\n      \"y\": 152.5,\n      \"width\": 184,\n      \"height\": 84,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 891739170,\n      \"version\": 42,\n      \"versionNonce\": 1136678334,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"LU4D6A2Ugd1V9uKE6SwOC\",\n        \"xm8fFB4fOVowURVtFEyfx\",\n        \"E-k0fg9CKUsCbBcIFgpQN\",\n        \"iqQRk3oncpFlTohh4RxWf\",\n        \"S1o9eYMClf4Mrmfw9HlDs\",\n        \"nbIlU5kICCXoOhMWP1aoq\",\n        \"4mPQElLVeuU0MBB9zyNTL\"\n      ],\n      \"updated\": 1639394052440\n    },\n    {\n      \"id\": \"6Ym2F9bT0rNpkiLbkm6Ku\",\n      \"type\": \"text\",\n      \"x\": 414.6666564941406,\n      \"y\": 185.50003051757812,\n      \"width\": 112,\n      \"height\": 20,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 162216574,\n      \"version\": 33,\n      \"versionNonce\": 199316542,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639393963543,\n      \"text\": \"rathole server\",\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 14\n    },\n    {\n      \"id\": \"72LJc8JYfizCW-59n-YiJ\",\n      \"type\": \"diamond\",\n      \"x\": 313.9999694824219,\n      \"y\": 456.83331298828125,\n      \"width\": 172.66668701171875,\n      \"height\": 74.66668701171876,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 627082402,\n      \"version\": 203,\n      \"versionNonce\": 1475968062,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"xm8fFB4fOVowURVtFEyfx\",\n        \"CYPbqJ97T4dK8aTY2NoA6\",\n        \"xQjRQnu2M-Lx4L_FApAWi\",\n        \"ZBwjcWgJYIRx-XieGSul2\",\n        \"DjwSuFQtjGNkkF4rl7myd\",\n        \"8qillKpd5VKO0hrasQMVX\"\n      ],\n      \"updated\": 1639394068076\n    },\n    {\n      \"id\": \"mR2qjxJFdOso9NGgCoq4h\",\n      \"type\": \"rectangle\",\n      \"x\": 705.3333740234375,\n      \"y\": 176.83335876464844,\n      \"width\": 24.6666259765625,\n      \"height\": 24.6666259765625,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 1143103778,\n      \"version\": 45,\n      \"versionNonce\": 1343517282,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"-lU_z4mfDB58ZiJ8HlTxY\",\n        \"LU4D6A2Ugd1V9uKE6SwOC\"\n      ],\n      \"updated\": 1639393963543\n    },\n    {\n      \"id\": \"rp7H2PQFGWvQJIbz1y8IG\",\n      \"type\": \"rectangle\",\n      \"x\": 705.3333740234375,\n      \"y\": 220.83335876464844,\n      \"width\": 24.6666259765625,\n      \"height\": 24.6666259765625,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 764034658,\n      \"version\": 77,\n      \"versionNonce\": 1528280446,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"-lU_z4mfDB58ZiJ8HlTxY\",\n        \"LU4D6A2Ugd1V9uKE6SwOC\",\n        \"n9WWKSJRRhkFG2L3AY6W_\",\n        \"4mPQElLVeuU0MBB9zyNTL\"\n      ],\n      \"updated\": 1639394052440\n    },\n    {\n      \"id\": \"5H4DUHb4ELZWIIpXO32ix\",\n      \"type\": \"rectangle\",\n      \"x\": 385.3333740234375,\n      \"y\": 284.16668701171875,\n      \"width\": 22,\n      \"height\": 22,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 1387104638,\n      \"version\": 257,\n      \"versionNonce\": 947580094,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"uJx77oj5eyZPw61wszaJN\",\n        \"CYPbqJ97T4dK8aTY2NoA6\",\n        \"E-k0fg9CKUsCbBcIFgpQN\",\n        \"iqQRk3oncpFlTohh4RxWf\",\n        \"xQjRQnu2M-Lx4L_FApAWi\",\n        \"ZBwjcWgJYIRx-XieGSul2\",\n        \"S1o9eYMClf4Mrmfw9HlDs\",\n        \"nbIlU5kICCXoOhMWP1aoq\"\n      ],\n      \"updated\": 1639393977422\n    },\n    {\n      \"id\": \"1-O9JOrs2pnONGNtZiH4B\",\n      \"type\": \"text\",\n      \"x\": 349.1666564941406,\n      \"y\": 482.8332824707031,\n      \"width\": 105,\n      \"height\": 20,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 1412742590,\n      \"version\": 106,\n      \"versionNonce\": 541336190,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394067926,\n      \"text\": \"rathole client\",\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"baseline\": 14\n    },\n    {\n      \"id\": \"CYPbqJ97T4dK8aTY2NoA6\",\n      \"type\": \"arrow\",\n      \"x\": 400.5230856224749,\n      \"y\": 452.05962166754307,\n      \"width\": 3.021429201284718,\n      \"height\": 139.89293465582432,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 941766434,\n      \"version\": 627,\n      \"versionNonce\": 840863074,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394067926,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -3.021429201284718,\n          -139.89293465582432\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"72LJc8JYfizCW-59n-YiJ\",\n        \"focus\": 0.012722437706498701,\n        \"gap\": 4.777461928511805\n      },\n      \"endBinding\": {\n        \"elementId\": \"5H4DUHb4ELZWIIpXO32ix\",\n        \"focus\": -0.07128881792747094,\n        \"gap\": 6\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"iqQRk3oncpFlTohh4RxWf\",\n      \"type\": \"arrow\",\n      \"x\": 411.88175007980965,\n      \"y\": 278.4142786269854,\n      \"width\": 41.1875896678809,\n      \"height\": 38.07827569869187,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 316085374,\n      \"version\": 241,\n      \"versionNonce\": 705621374,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639393963544,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          41.1875896678809,\n          -38.07827569869187\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"5H4DUHb4ELZWIIpXO32ix\",\n        \"focus\": -0.111311585930796,\n        \"gap\": 7.333343505859375\n      },\n      \"endBinding\": {\n        \"elementId\": \"NzpaVP1cgsvfg6KfdD99G\",\n        \"focus\": -0.4128416678755755,\n        \"gap\": 8.3058554409374\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"xQjRQnu2M-Lx4L_FApAWi\",\n      \"type\": \"arrow\",\n      \"x\": 415.03538422542744,\n      \"y\": 453.3711527236708,\n      \"width\": 10.189618736042235,\n      \"height\": 139.87115272367078,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 419928446,\n      \"version\": 136,\n      \"versionNonce\": 325751074,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394067926,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -10.189618736042235,\n          -139.87115272367078\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"72LJc8JYfizCW-59n-YiJ\",\n        \"focus\": 0.20468988783315253,\n        \"gap\": 9.013184853545113\n      },\n      \"endBinding\": {\n        \"elementId\": \"5H4DUHb4ELZWIIpXO32ix\",\n        \"focus\": -0.6081345501761519,\n        \"gap\": 7.33331298828125\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"ZBwjcWgJYIRx-XieGSul2\",\n      \"type\": \"arrow\",\n      \"x\": 381.63001651123903,\n      \"y\": 456.0383959625252,\n      \"width\": 2.158517689316966,\n      \"height\": 145.46817028787365,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 39236898,\n      \"version\": 133,\n      \"versionNonce\": 1006640354,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394067926,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          2.158517689316966,\n          -145.46817028787365\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"72LJc8JYfizCW-59n-YiJ\",\n        \"focus\": -0.22319845234228697,\n        \"gap\": 8.153168060821436\n      },\n      \"endBinding\": {\n        \"elementId\": \"5H4DUHb4ELZWIIpXO32ix\",\n        \"focus\": 1.1032903390278315,\n        \"gap\": 4.666656494140625\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"S1o9eYMClf4Mrmfw9HlDs\",\n      \"type\": \"arrow\",\n      \"x\": 418.66668701171875,\n      \"y\": 287.5,\n      \"width\": 38.666656494140625,\n      \"height\": 43.33331298828125,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 247933282,\n      \"version\": 30,\n      \"versionNonce\": 1800972706,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639393973403,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          38.666656494140625,\n          -43.33331298828125\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"5H4DUHb4ELZWIIpXO32ix\",\n        \"focus\": 0.7442699983400397,\n        \"gap\": 11.33331298828125\n      },\n      \"endBinding\": {\n        \"elementId\": \"NzpaVP1cgsvfg6KfdD99G\",\n        \"focus\": -0.4020068751542848,\n        \"gap\": 10.019774658829391\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"nbIlU5kICCXoOhMWP1aoq\",\n      \"type\": \"arrow\",\n      \"x\": 403.3333435058594,\n      \"y\": 273.5,\n      \"width\": 44,\n      \"height\": 37.33331298828125,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 261563746,\n      \"version\": 26,\n      \"versionNonce\": 1384498722,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639393977422,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          44,\n          -37.33331298828125\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"5H4DUHb4ELZWIIpXO32ix\",\n        \"focus\": -0.7734750559093652,\n        \"gap\": 10.66668701171875\n      },\n      \"endBinding\": {\n        \"elementId\": \"NzpaVP1cgsvfg6KfdD99G\",\n        \"focus\": -0.6418956116222861,\n        \"gap\": 6.895194123821575\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"7N39v3qK0fltyhClnuI_Q\",\n      \"type\": \"ellipse\",\n      \"x\": 582.0000305175781,\n      \"y\": 444.16668701171875,\n      \"width\": 112.6666259765625,\n      \"height\": 47.33331298828125,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 1272534334,\n      \"version\": 116,\n      \"versionNonce\": 1964223486,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"DjwSuFQtjGNkkF4rl7myd\"\n      ],\n      \"updated\": 1639394068076\n    },\n    {\n      \"id\": \"THrrqy4Axfy1vlF2wrI9s\",\n      \"type\": \"ellipse\",\n      \"x\": 582.0000305175781,\n      \"y\": 498.8333740234375,\n      \"width\": 112.6666259765625,\n      \"height\": 47.33331298828125,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 614595234,\n      \"version\": 142,\n      \"versionNonce\": 909853822,\n      \"isDeleted\": false,\n      \"boundElementIds\": [\n        \"8qillKpd5VKO0hrasQMVX\"\n      ],\n      \"updated\": 1639394068076\n    },\n    {\n      \"id\": \"WyAj01yc3DnhvWQG7tHd9\",\n      \"type\": \"text\",\n      \"x\": 605.8333435058594,\n      \"y\": 457.8333435058594,\n      \"width\": 65,\n      \"height\": 20,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 2040747710,\n      \"version\": 45,\n      \"versionNonce\": 172071906,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394067926,\n      \"text\": \"service 1\",\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"baseline\": 14\n    },\n    {\n      \"id\": \"CDfW7H0EVISeS0Zugsf8W\",\n      \"type\": \"text\",\n      \"x\": 600.9999694824219,\n      \"y\": 512.5000305175781,\n      \"width\": 72,\n      \"height\": 20,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"sharp\",\n      \"seed\": 1292334754,\n      \"version\": 74,\n      \"versionNonce\": 1763707710,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394067926,\n      \"text\": \"service 2\",\n      \"fontSize\": 16,\n      \"fontFamily\": 1,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"baseline\": 14\n    },\n    {\n      \"id\": \"DjwSuFQtjGNkkF4rl7myd\",\n      \"type\": \"arrow\",\n      \"x\": 493.3333435058594,\n      \"y\": 489.0001220703125,\n      \"width\": 80.49465291276579,\n      \"height\": 19.591364584118082,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 1489129058,\n      \"version\": 177,\n      \"versionNonce\": 1120784162,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394068076,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          80.49465291276579,\n          -19.591364584118082\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"72LJc8JYfizCW-59n-YiJ\",\n        \"focus\": 0.46790554502387516,\n        \"gap\": 7.388222614736868\n      },\n      \"endBinding\": {\n        \"elementId\": \"7N39v3qK0fltyhClnuI_Q\",\n        \"focus\": 0.5164042977199623,\n        \"gap\": 8.24035262874532\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"8qillKpd5VKO0hrasQMVX\",\n      \"type\": \"arrow\",\n      \"x\": 497.3333435058594,\n      \"y\": 496.33331298828125,\n      \"width\": 78,\n      \"height\": 22,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 1551722530,\n      \"version\": 144,\n      \"versionNonce\": 1536814754,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394068076,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          78,\n          22\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"72LJc8JYfizCW-59n-YiJ\",\n        \"focus\": -0.6747942752141096,\n        \"gap\": 6.222408426625634\n      },\n      \"endBinding\": {\n        \"elementId\": \"THrrqy4Axfy1vlF2wrI9s\",\n        \"focus\": -0.4771879886646304,\n        \"gap\": 7.177668745668626\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    },\n    {\n      \"id\": \"n9WWKSJRRhkFG2L3AY6W_\",\n      \"type\": \"arrow\",\n      \"x\": 876.3054169557988,\n      \"y\": 206.15009644516743,\n      \"width\": 139.63872994408007,\n      \"height\": 28.349903554832565,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 1631704318,\n      \"version\": 50,\n      \"versionNonce\": 1307848610,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394048593,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -139.63872994408007,\n          28.349903554832565\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": null,\n      \"endBinding\": {\n        \"elementId\": \"rp7H2PQFGWvQJIbz1y8IG\",\n        \"focus\": 0.3498468485388594,\n        \"gap\": 6.66668701171875\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"4mPQElLVeuU0MBB9zyNTL\",\n      \"type\": \"arrow\",\n      \"x\": 696.6666870117188,\n      \"y\": 231.83334350585938,\n      \"width\": 140.66668701171875,\n      \"height\": 30.666656494140625,\n      \"angle\": 0,\n      \"strokeColor\": \"#000000\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"strokeSharpness\": \"round\",\n      \"seed\": 1042183102,\n      \"version\": 16,\n      \"versionNonce\": 392654114,\n      \"isDeleted\": false,\n      \"boundElementIds\": null,\n      \"updated\": 1639394053746,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -140.66668701171875,\n          -30.666656494140625\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"rp7H2PQFGWvQJIbz1y8IG\",\n        \"focus\": -0.21600645731035134,\n        \"gap\": 8.66668701171875\n      },\n      \"endBinding\": {\n        \"elementId\": \"NzpaVP1cgsvfg6KfdD99G\",\n        \"focus\": -0.31535312984667163,\n        \"gap\": 5.787735184532465\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": null\n    }\n  ],\n  \"appState\": {\n    \"gridSize\": null,\n    \"viewBackgroundColor\": \"#ffffff\"\n  },\n  \"files\": {}\n}"
  },
  {
    "path": "docs/internals.md",
    "content": "# Internals\n\n![overview](./img/overview.png)\n\n## Conceptions\n### Service\nThe entity whose traffic needs to be forwarded\n\n### Server\nThe host that runs `rathole` in the server mode\n\n### Client\nThe host behind the NAT that runs `rathole` in the client mode. It has some services that need to be forwarded.\n\n### Visitor\nWho visists a *service*, via the *server*\n\n### Control Channel\nA control channel is a TCP connection between the *server* and the *client* that only carries `rathole` control commands for one *service*.\n\n### Data Channel\n\nA data channel is a TCP connection between the *server* and the *client* that only carries the encapsulated data that needs forwarding for one *service*.\n\n## The Process\n\n*TODO: Add more details about the protocol*\n\nWhen `rathole` starts in the client mode, it creates connections to `server.common.bind_addr` for each service. These connection acts as control channels.\n\nWhen a control channel starts, the server challenge the client by a nonce, the client is required to authenticate as the service it wants to represent. Then the forwarding of that service is set up.\n\nWhen the server accepts a connection on a service's `bind_port`, it sends a control command to the client via the corresponding control channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency.\n\n"
  },
  {
    "path": "docs/out-of-scope.md",
    "content": "# Out of Scope\n\n`rathole` focuses on the forwarding for the NAT traversal, rather than being a all-in-one development tool or a load balancer or a gateway. It's designed to *be used with them*, not *replace them*.\n\nBut that doesn't mean it's not useful for other purposes. In the future, more configuration APIs will be added and `rathole` can be used with an external dashboard.\n\n> Make each program do one thing well.\n\n- *Domain based forwarding for HTTP*\n\n  Introducing these kind of features into `rathole` itself ultimately reinvent a nginx. Use nginx to do this and set `rathole` as the upstream. This method achieves better performance as well as flexibility.\n\n- *HTTP Request Logging*\n\n  `rathole` doesn't interference with the application layer traffic. A right place for this kind of stuff is the web server, and a network capture tool.\n\n- *`frp`'s STCP or other setup that requires visitors' side configuration*\n\n  If that kind of setup is possible, then there are a lot more tools available. You may want to consider secure tunnels like wireguard or zerotier. `rathole` primarily focuses on NAT traversal by forwarding, which doesn't require any setup for visitors. \n\n- *Caching `local_ip`'s DNS records*\n\n  As responded in [issue #183](https://github.com/rapiz1/rathole/issues/183), `local_ip` cache is not feasible because we have no reliable way to detect ip change. Handle DNS TTL and so on should be done with a DNS server, not a client. Caching ip is generally dangerous for clients. If you care about the `local_ip` query you can set up a local DNS server and enable caching. Then the local lookup should be trivial.\n"
  },
  {
    "path": "docs/transport.md",
    "content": "# Security\n\nBy default, `rathole` forwards traffic as it is. Different options can be enabled to secure the traffic.\n\n## TLS\n\nCheckout the [example](../examples/tls)\n\n### Client\n\nNormally, a self-signed certificate is used. In this case, the client needs to trust the CA. `trusted_root` is the path to the root CA's certificate PEM file.\n`hostname` is the hostname that the client used to validate aginst the certificate that the server presents. Note that it does not have to be the same with the `remote_addr` in `[client]`.\n\n```toml\n[client.transport.tls]\ntrusted_root = \"example/tls/rootCA.crt\"\nhostname = \"localhost\"\n```\n\n### Server\n\nPKCS#12 archives are needed to run the server.\n\nIt can be created using openssl like:\n\n```sh\nopenssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt\n```\n\nAruguments are:\n\n- `-inkey`: Server Private Key\n- `-in`: Server Certificate\n- `-certfile`: CA Certificate\n\nCreating self-signed certificate with one's own CA is a non-trival task. However, a script is provided under tls example folder for reference.\n\n### Rustls Support\n\n`rathole` provides optional `rustls` support. [Build Guide](build-guide.md) demostrated this.\n\nOne difference is that, the crate we use for loading PKCS#12 archives can only handle limited types of PBE algorithms. We only support PKCS#12 archives that they (crate `p12`) support. So we need to specify the legacy format (openssl 1.x format) when creating the PKCS#12 archive.\n\nIn short, the command used with openssl 3 to create the PKCS#12 archive with `rustls` support is:\n\n```sh\nopenssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt -legacy\n```\n\n## Noise Protocol\n\n### Quickstart for the Noise Protocl\n\nIn one word, the [Noise Protocol](http://noiseprotocol.org/noise.html) is a lightweigt, easy to configure and drop-in replacement of TLS. No need to create a self-sign certificate to secure the connection.\n\n`rathole` comes with a reasonable default configuration for noise protocol. You can a glimpse of the minimal [example](../examples/noise_nk) for how it will look like.\n\nThe default noise protocol that `rathole` uses, which is `Noise_NK_25519_ChaChaPoly_BLAKE2s`, providing the authentication of the server, just like TLS with properly configured certificates. So MITM is no more a problem.\n\nTo use it, a X25519 keypair is needed.\n\n#### Generate a Keypair\n\n1. Run `rathole --genkey`, which will generate a keypair using the default X25519 algorithm.\n\nIt emits:\n\n```sh\n$ rathole --genkey\nPrivate Key:\ncQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q=\n\nPublic Key:\nGQYTKSbWLBUSZiGfdWPSgek9yoOuaiwGD/GIX8Z1kkE=\n```\n\n(WARNING: Don't use the keypair from the Internet, including this one)\n\n2. The server should keep the private key to identify itself. And the client should keep the public key, which is used to verify whether the peer is the authentic server.\n\nSo relevant snippets of configuration are:\n\n```toml\n# Client Side Configuration\n[client.transport]\ntype = \"noise\"\n[client.transport.noise]\nremote_public_key = \"GQYTKSbWLBUSZiGfdWPSgek9yoOuaiwGD/GIX8Z1kkE=\"\n\n# Server Side Configuration\n[server.transport]\ntype = \"noise\"\n[server.transport.noise]\nlocal_private_key = \"cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q=\"\n```\n\nThen `rathole` will run under the protection of the Noise Protocol.\n\n## Specifying the Pattern of Noise Protocol\n\nThe default configuration of Noise Protocol that comes with `rathole` satifies most use cases, which is described above. But there're other patterns that can be useful.\n\n### No Authentication\n\nThis configuration provides encryption of the traffic but provides no authentication, which means it's vulnerable to MITM attack, but is resistent to the sniffing and replay attack. If MITM attack is not one of the concerns, this is more convenient to use.\n\n```toml\n# Server Side Configuration\n[server.transport.noise]\npattern = \"Noise_XX_25519_ChaChaPoly_BLAKE2s\"\n\n# Client Side Configuration\n[client.transport.noise]\npattern = \"Noise_XX_25519_ChaChaPoly_BLAKE2s\"\n```\n\n### Bidirectional Authentication\n\n```toml\n# Server Side Configuration\n[server.transport.noise]\npattern = \"Noise_KK_25519_ChaChaPoly_BLAKE2s\"\nlocal_private_key = \"server-priv-key-here\"\nremote_public_key = \"client-pub-key-here\"\n\n# Client Side Configuration\n[client.transport.noise]\npattern = \"Noise_KK_25519_ChaChaPoly_BLAKE2s\"\nlocal_private_key = \"client-priv-key-here\"\nremote_public_key = \"server-pub-key-here\"\n```\n\n### Other Patterns\n\nTo find out which pattern to use, refer to:\n\n- [7.5. Interactive handshake patterns (fundamental)](https://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental)\n- [8. Protocol names and modifiers](https://noiseprotocol.org/noise.html#protocol-names-and-modifiers)\n\nNote that PSKs are not supported currently. Free to open an issue if you need it.\n"
  },
  {
    "path": "examples/iperf3/client.toml",
    "content": "[client]\nremote_addr = \"localhost:2333\"\ndefault_token = \"123\"\n\n[client.services.iperf3-udp]\ntype = \"udp\"\nlocal_addr = \"127.0.0.1:80\"\n\n[client.services.iperf3-tcp]\ntype = \"tcp\"\nlocal_addr = \"127.0.0.1:80\"\n"
  },
  {
    "path": "examples/iperf3/server.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.services.iperf3-udp]\ntype = \"udp\"\nbind_addr = \"0.0.0.0:5202\"\n\n[server.services.iperf3-tcp]\ntype = \"tcp\"\nbind_addr = \"0.0.0.0:5202\"\n"
  },
  {
    "path": "examples/minimal/client.toml",
    "content": "[client]\nremote_addr = \"localhost:2333\"\ndefault_token = \"123\"\n\n[client.services.foo1]\nlocal_addr = \"127.0.0.1:80\"\n"
  },
  {
    "path": "examples/minimal/server.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.services.foo1]\nbind_addr = \"0.0.0.0:5202\"\n"
  },
  {
    "path": "examples/noise_nk/client.toml",
    "content": "[client]\nremote_addr = \"localhost:2333\"\ndefault_token = \"123\"\n\n[client.transport]\ntype = \"noise\"\n[client.transport.noise]\nremote_public_key = \"xrpknQcAagcd/b9foMwxSCD+EindWxq450NEONk8XQo=\"\n\n[client.services.foo1]\nlocal_addr = \"127.0.0.1:80\"\n"
  },
  {
    "path": "examples/noise_nk/server.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.transport]\ntype = \"noise\"\n[server.transport.noise]\nlocal_private_key = \"QLYMByBnjgM254zT6YKaBVvuAA61swyZfFxoA/SKZHM=\"\n\n[server.services.foo1]\nbind_addr = \"0.0.0.0:5202\"\n"
  },
  {
    "path": "examples/systemd/README.md",
    "content": "## Systemd Unit Examples\n\nThe directory lists some systemd unit files for example, which can be used to run `rathole` as a service on Linux.\n\n[The `@` symbol in the name of unit files](https://superuser.com/questions/393423/the-symbol-and-systemctl-and-vsftpd) such as\n`rathole@.service` facilitates the management of multiple instances of `rathole`.\n\nFor the naming of the example, `ratholes` stands for `rathole --server`, and `ratholec` stands for `rathole --client`, `rathole` is just `rathole`.\n\nFor security, it is suggested to store configuration files with permission `600`, that is, only the owner can read the file, preventing arbitrary users on the system from accessing the secret tokens.\n\n### With root privilege\n\nAssuming that `rathole` is installed in `/usr/bin/rathole`, and the configuration file is in `/etc/rathole/app1.toml`, the following steps show how to run an instance of `rathole --server` with root.\n\n1. Create a service file.\n\n```bash\nsudo cp ratholes@.service /etc/systemd/system/\n```\n\n2. Create the configuration file `app1.toml`.\n\n```bash\nsudo mkdir -p /etc/rathole\n# And create the configuration file named `app1.toml` inside /etc/rathole\n```\n\n3. Enable and start the service.\n\n```bash\nsudo systemctl daemon-reload # Make sure systemd find the new unit\nsudo systemctl enable ratholes@app1 --now\n```\n\n### Without root privilege\n\nAssuming that `rathole` is installed in `~/.local/bin/rathole`, and the configuration file is in `~/.local/etc/rathole/app1.toml`, the following steps show how to run an instance of `rathole --server` without root.\n\n1. Edit the example service file as...\n\n```txt\n# with root\n# ExecStart=/usr/bin/rathole -s /etc/rathole/%i.toml\n# without root\nExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/%i.toml\n```\n\n2. Create a service file.\n\n```bash\nmkdir -p ~/.config/systemd/user\ncp ratholes@.service ~/.config/systemd/user/\n```\n\n3. Create the configuration file `app1.toml`.\n\n```bash\nmkdir -p ~/.local/etc/rathole\n# And create the configuration file named `app1.toml` inside ~/.local/etc/rathole\n```\n\n4. Enable and start the service.\n\n```bash\nsystemctl --user daemon-reload # Make sure systemd find the new unit\nsystemctl --user enable ratholes@app1 --now\n```\n\n### Run multiple services\n\nTo run multiple services at once, simply add another configuration, say `app2.toml` under `/etc/rathole` (`~/.local/etc/rathole` for non-root), then run `sudo systemctl enable ratholes@app2 --now` (`systemctl --user enable ratholes@app2 --now` for non-root) to start an instance for that configuration.\n\nThe same applies to `ratholec@.service` for `rathole --client` and `rathole@.service` for `rathole`.\n"
  },
  {
    "path": "examples/systemd/rathole@.service",
    "content": "[Unit]\nDescription=Rathole Service\nAfter=network.target\n\n[Service]\nType=simple\nRestart=on-failure\nRestartSec=5s\nLimitNOFILE=1048576\n\n# with root\nExecStart=/usr/bin/rathole /etc/rathole/%i.toml\n# without root\n# ExecStart=%h/.local/bin/rathole %h/.local/etc/rathole/%i.toml\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "examples/systemd/ratholec.service",
    "content": "[Unit]\nDescription=Rathole Client Service\nAfter=network.target\n\n[Service]\nType=simple\nRestart=on-failure\nRestartSec=5s\nLimitNOFILE=1048576\n\n# with root\nExecStart=/usr/bin/rathole -c /etc/rathole/rathole.toml\n# without root\n# ExecStart=%h/.local/bin/rathole -c %h/.local/etc/rathole/rathole.toml\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "examples/systemd/ratholec@.service",
    "content": "[Unit]\nDescription=Rathole Client Service\nAfter=network.target\n\n[Service]\nType=simple\nRestart=on-failure\nRestartSec=5s\nLimitNOFILE=1048576\n\n# with root\nExecStart=/usr/bin/rathole -c /etc/rathole/%i.toml\n# without root\n# ExecStart=%h/.local/bin/rathole -c %h/.local/etc/rathole/%i.toml\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "examples/systemd/ratholes.service",
    "content": "[Unit]\nDescription=Rathole Server Service\nAfter=network.target\n\n[Service]\nType=simple\nRestart=on-failure\nRestartSec=5s\nLimitNOFILE=1048576\n\n# with root\nExecStart=/usr/bin/rathole -s /etc/rathole/rathole.toml\n# without root\n# ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/rathole.toml\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "examples/systemd/ratholes@.service",
    "content": "[Unit]\nDescription=Rathole Server Service\nAfter=network.target\n\n[Service]\nType=simple\nRestart=on-failure\nRestartSec=5s\nLimitNOFILE=1048576\n\n# with root\nExecStart=/usr/bin/rathole -s /etc/rathole/%i.toml\n# without root\n# ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/%i.toml\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "examples/tls/client.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\"\ndefault_token = \"123\"\n\n[client.transport]\ntype = \"tls\"\n[client.transport.tls]\ntrusted_root = \"examples/tls/rootCA.crt\"\nhostname = \"localhost\"\n\n[client.services.foo1]\nlocal_addr = \"127.0.0.1:80\"\n"
  },
  {
    "path": "examples/tls/create_self_signed_cert.sh",
    "content": "#!/bin/sh\n\n# create CA \nopenssl req -x509 \\\n            -sha256 -days 356 \\\n            -nodes \\\n            -newkey rsa:2048 \\\n            -subj \"/CN=MyOwnCA/C=US/L=San Fransisco\" \\\n            -keyout rootCA.key -out rootCA.crt \n\n# create server private key\nopenssl genrsa -out server.key 2048\n\n# create certificate signing request (CSR)\ncat > csr.conf <<EOF\n[ req ]\ndefault_bits = 2048\nprompt = no\ndefault_md = sha256\nreq_extensions = req_ext\ndistinguished_name = dn\n\n[ dn ]\nC = US\nST = California\nL = San Fransisco\nO = Someone\nOU = Someone\nCN = localhost\n\n[ req_ext ]\nsubjectAltName = @alt_names\n\n[ alt_names ]\nDNS.1 = localhost\nEOF\n\nopenssl req -new -key server.key -out server.csr -config csr.conf\n\n# create server cert\ncat > cert.conf <<EOF\nauthorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = localhost\nEOF\n\nopenssl x509 -req \\\n    -in server.csr \\\n    -CA rootCA.crt -CAkey rootCA.key \\\n    -out server.crt \\\n    -days 365 \\\n    -sha256 -extfile cert.conf\n\n# create pkcs12\nopenssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile rootCA.crt \\\n    -passout pass:1234 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES\n\n# clean up\nrm server.csr csr.conf cert.conf\n"
  },
  {
    "path": "examples/tls/rootCA.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDTzCCAjegAwIBAgIUOSG1er7cfoTq6uMOe3r0tcSZREMwDQYJKoZIhvcNAQEL\nBQAwNzEQMA4GA1UEAwwHTXlPd25DQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNh\nbiBGcmFuc2lzY28wHhcNMjUwNzI2MTI0MzIwWhcNMjYwNzE3MTI0MzIwWjA3MRAw\nDgYDVQQDDAdNeU93bkNBMQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5z\naXNjbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOqTqiNhaLU5iKRL\nUSb4LxgxYdS4Ir8sC0usNToVivCNSugm0OGdwCgB/ARm4lQzRm1P0zfgOOO+Sm03\nQhjhV9Ds8c4JiECLT+S0EG7aNLgtCBx2xrIw0K+XeTbY/bVZTYIK6q9kWcK3r1WX\nItZXHAArbNRo995jwz2iE//Kq0nPDeyP3PUrsqrsdpy/oZLXtIWmtzxJskM99zi8\nxBTH7uJnosSOLuo31o59Qag/SPOQbEUpl5w/9KxMUgWZxBz04mQE3KAlU4PFPYBV\n48035S+24bd8T03d0DyqTI34dGweFKV3aaWYT40l3ivXLtrqFKQZOrSeQMAXd8At\n1qInNJECAwEAAaNTMFEwHQYDVR0OBBYEFMkX5ozB+FrRgh36PnPe2Ps9L7igMB8G\nA1UdIwQYMBaAFMkX5ozB+FrRgh36PnPe2Ps9L7igMA8GA1UdEwEB/wQFMAMBAf8w\nDQYJKoZIhvcNAQELBQADggEBAC/BqqAPGOfkMk6mBZRyOWWoTWFo4keiJBDmyAIq\n0A8v9WtDKr/V4/Y2Y/SLdLO2nP58jomnFguYICERJXsEJeDdMIP/4v3+htVTMZDl\n4iExaxZEgLFv1bnaGhsaHhytzuDHWZMpqp427ZXCFTPEzT/iFH4waTNaYpXWh26M\nGXzl1iyGoIy9KOe2p+2sN7XAjroGIF9JKQt7rCXJ8Om0Uxvn/Ic7HPEFJ6RAAUMU\ndmOaDJq0wsi9AxmqJl0m2Z+MJjPN717BNzavOebCa8xrlrXzlruHDrwo4StLG2wi\n35eSgJLyz5hecm2pQLSRJfL+Xcp8iMVupZFvO92pWKmjk+s=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/tls/rootCA.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqk6ojYWi1OYik\nS1Em+C8YMWHUuCK/LAtLrDU6FYrwjUroJtDhncAoAfwEZuJUM0ZtT9M34Djjvkpt\nN0IY4VfQ7PHOCYhAi0/ktBBu2jS4LQgcdsayMNCvl3k22P21WU2CCuqvZFnCt69V\nlyLWVxwAK2zUaPfeY8M9ohP/yqtJzw3sj9z1K7Kq7Hacv6GS17SFprc8SbJDPfc4\nvMQUx+7iZ6LEji7qN9aOfUGoP0jzkGxFKZecP/SsTFIFmcQc9OJkBNygJVODxT2A\nVePNN+UvtuG3fE9N3dA8qkyN+HRsHhSld2mlmE+NJd4r1y7a6hSkGTq0nkDAF3fA\nLdaiJzSRAgMBAAECggEASE417mvzI5FVbhcNL67mjVWDa1dK1pST8sJlMb88MYPL\n6B0226SNe4eJEC5Ka1vWxJELcTi7MHASbvHOfO7Q68RtkG3dws9uU/ew7Qpzn6W5\nz1RJUNm9KbLOGTrvkTuyqXgF+QQ8qsmF0SMiCOGW7vJzvFGSvckQgGn6MildfQUv\nfBzRPsGOQXFces2bhC+92FH6u2HzMN/CBhoBt0HowyFjs+Gh20iB/7rkXMZ3Re/H\n7jIwCkhPCKMlXbMYlzuot+d1tEXEgKSo81tjet7wqp3dZNOum0oIiZPrLMZyMHeD\nBHSXjChCQs8iSyaH2xTrihsrhP0VIfLLjTlHsoaXVQKBgQD+4UIeG4ztos+p/wdt\nyaHEHNjp0/LGLfaCACREFBGIsov8qkRrNqwE9Lqrd4VPOcGyDqY81rWPTIeOSArs\npjkD352o73fjVCbSh3TgOZuEhCZNxJVL8cxTTB+JTrlW2xiDNN3pT2mhSqGwnb9I\nncd30FlywQ9PC9pSGjyMhqbAMwKBgQDrm5CwxrPU2N+uHGglKhO8OQFD6uJqVIdn\nw7cuywxyNnigf3mU5wFQ3nJ7VBxTxcHUDYDA8fdqKj/xUC/uvUECMjtCFvPP724J\n80K8GYEwIH0Mm+QeRtViSAP3icq0f6070us9Y+jF6zWhsjmsABGPXlQBR8kpJX7C\neM4ChatkKwKBgQD5QppCj3b3P24PNhWxzKqi/AM2Scz2+yREZpcQ7P9ozBQS+QEI\nSONZxWx5G94HaNiApcr1XJUamyFGiDYG0ViY7Stmdyqr6zQ8V7R5RF1O7132V/YZ\n21KTc7KzuYWP55zFVAJhlQQcdLxD2UGkZokYJ24Sx7OM/m54NKhaVaTl+QKBgGpb\n6sByouHWGXvO5RDJ4ujYTwLq+NUJXarxBjPAg9jXUDMb+LXIZqasFMAp4zPKFUr1\n4Ya4dHlfo0f7a/f5RWyJYojeNahLrMAfKaQiW1hvgiP8B8nHLjLU0b2gXXqIHJri\nB0HKZV1bZfWdsD0+Nq8i5QdC8cN6YrPFtzIaR5nLAoGBAJquKGdlCZErAWVnBOpj\nCujXpMBQJa7+ZWY5q+19s99jzffSdyMhuHbVf3NxE0fu8+A3csZDDlW2wyIIM6tk\nHKxJSUapv7MDiA1nz3KlewJEr9XAHnapNciur6EgxEKPhjY86I76b28xR2Tdj4zY\nwI+K8vATq4tFiSqM4hil+4WG\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "examples/tls/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDpzCCAo+gAwIBAgIUPP/pKlmn1kOLpd31HXRSjtw2muQwDQYJKoZIhvcNAQEL\nBQAwNzEQMA4GA1UEAwwHTXlPd25DQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNh\nbiBGcmFuc2lzY28wHhcNMjUwNzI2MTI0MzIwWhcNMjYwNzI2MTI0MzIwWjByMQsw\nCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZy\nYW5zaXNjbzEQMA4GA1UECgwHU29tZW9uZTEQMA4GA1UECwwHU29tZW9uZTESMBAG\nA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\np1LrbPzkvfwPzcBgORrmVxzkB+TKPdg+P5Ef6IrtlX3juyjUKMPK67v0zDwNKKyI\n9LYND4KHImhgBcDc4M1B5y5WdALFR6XkJhcRtdm8RWY4FYalI0Q9ZbIzDZVVtIK5\nkJx7r3t60rlSuHC55f3cJpek+BNAtqf/XiDgVAtSp7EECdkODFbWKhOLnln3Lvvs\n6xkOOpSy7grYWUHMmUCv2LdLZuripjWJdT4Vkm+jTR+m1cnItruJZ3uGI2xmLRwy\ngg9BkWG6Yn6Sr8is9I22yiFRKW/6SYkCZ19VwCzOahuQR5t0K1DrPjjI1nCbMaak\n9fcRnz8X0JRQ5NloaGyynQIDAQABo3AwbjAfBgNVHSMEGDAWgBTJF+aMwfha0YId\n+j5z3tj7PS+4oDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAUBgNVHREEDTALggls\nb2NhbGhvc3QwHQYDVR0OBBYEFNXm+idP/nizMy9EbGzf64KoObQNMA0GCSqGSIb3\nDQEBCwUAA4IBAQB3MhVio45UevtifaWfzQqhbK+QYUrKTUVzvFzkdnIB+c5xkl5p\nMaYZkWaRZxg9ZAKRIv+sNWw9sCBFtXsnxHyaTfwXx6qE5nPHBgSWUogIRh1aJadY\nw9QxCyBwO72aUguDskGYNX7fP69RpxFQXJYlwfpH/Z9UMTb7dE78iuU2GpiHxLql\n4oeMvCGqDV3d9aDBBOqNu/WsU9wcvakDF8E0Vx45kU0ze/Fr2R1LYagsQ0vaabPi\nw6X9V1iUMuvpsiqcWjdj5WgMRXqRd5+CrDmXG3dsDBWB0LMkU0Vm1s3hso/9clx2\nznZJJliEWwvHnBTcuXUQBNJP10NxWnDeV7tl\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/tls/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCnUuts/OS9/A/N\nwGA5GuZXHOQH5Mo92D4/kR/oiu2VfeO7KNQow8rru/TMPA0orIj0tg0PgociaGAF\nwNzgzUHnLlZ0AsVHpeQmFxG12bxFZjgVhqUjRD1lsjMNlVW0grmQnHuve3rSuVK4\ncLnl/dwml6T4E0C2p/9eIOBUC1KnsQQJ2Q4MVtYqE4ueWfcu++zrGQ46lLLuCthZ\nQcyZQK/Yt0tm6uKmNYl1PhWSb6NNH6bVyci2u4lne4YjbGYtHDKCD0GRYbpifpKv\nyKz0jbbKIVEpb/pJiQJnX1XALM5qG5BHm3QrUOs+OMjWcJsxpqT19xGfPxfQlFDk\n2WhobLKdAgMBAAECggEAGC+LKiDP64pAuovhHL//pX3elCmE9jWIoeWXSCS0vNQK\nk+YN7KqIbVSoyNCxUjzGOyEJeEF0yQtvPcWn57KjoQf4pSI1Y+rdtIfHHpMPfLSO\nzMW+nv9hJI8ChCce0U3IBtgnpLDjiwwQoephZJYyOT3YMaeOxhg4eGGmK1/LncNj\not4OORaDHINq25Mt21BxhYaBG8p4DRfyb0HVXU9bQcjbLQViDFcDnoQB2F/lz4EO\nV9r+IxE0/TKaGCqyEKhCjW303mBWlHpU66U+ykCZBLUmieOYmWEecp+liUYFo874\nTiM3UXj7mQ1sBOkIrIhnYNLKPF5pxdsRvVIadIlyfQKBgQDW/YGL8ODpCllfdMcr\nCymZj2zoKSwG2/N3Ee1G0EO2areYEiUlhImtz6tif4Z4Zjli7ZaoojfkGT++4U+L\n6gH6A0Att+KMFkDbaiTPbaUXYywSYkhj+il/oz6aumqfP+hy9qCLT1wXnryMpH06\n4YEZkwp7OuK8KiwRkJs46YDwbwKBgQDHPcAW3xiGfEVPWRui3FWj+NgnaUagPvOV\n8XeVSwk6XW4RfJdISVdT1gKtjPV8UJ8kSogW6tVrWtZ73fd/uLrYwdCp9btBu7Dv\nrA6nMeg8rLPr0Aq9Obv+ANk/TtgFqQjfZqqbxISspzazM1rBQq+/njXt8jFwliSW\n/CX6WVU7swKBgQDG1l2rVRRe1ICGRZYzXDaUXM9oBTQ//8u0U3M1bEdD/n1g+185\nzNQdWhVzmuh+kGUA9ybBPo8curF3VCFjEQHU/o6r+gdgcvB3PjtfUVRARiursRSs\nyuD4uL2dE06rjMrrEOi5D6PoAJr4JOXhwFDLm3A1OugbCZIKiMjXITdspwKBgFTw\nM0rM0yTKJ7YbE9gLPItJ08SBcfVwwOFkbol70rRKDllwFwJfGdaIvt2D4UedCysq\nhvfWJyO8NwHZb+DIPQeZIL7EHo94V4blf92xPgNX0OAv8dQXn9g6PmNp1lgbZsfu\neb8sOS9tnbkppIANUOVMqksFXCRWLcUcO4iNuvNXAoGANA/bSU1uJTw0tQMyewcL\nX0kskpHiEICH131yQgXsOI38uGAz7F4/i8OSQ77hNSF2b7XcNwf2rNkybswyulMu\n4SfhKDHslFSHW3iMXNDxh3YHk71I+n9cXVjPtW5WuxFn7plJM6zvhzPy2xpGqoPL\nFUw5z8qWROqGOrXenjAWdnw=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "examples/tls/server.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.transport]\ntype = \"tls\"\n[server.transport.tls]\npkcs12 = \"examples/tls/identity.pfx\"\npkcs12_password = \"1234\"\n\n[server.services.foo1]\nbind_addr = \"0.0.0.0:5202\"\n"
  },
  {
    "path": "examples/udp/client.toml",
    "content": "[client]\nremote_addr = \"localhost:2333\"\ndefault_token = \"123\"\n\n[client.services.foo1]\ntype = \"udp\"\nlocal_addr = \"127.0.0.1:80\"\n"
  },
  {
    "path": "examples/udp/server.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.services.foo1]\ntype = \"udp\"\nbind_addr = \"0.0.0.0:5202\"\n"
  },
  {
    "path": "examples/unified/config.toml",
    "content": "# rathole configuration can put in one file as long as running mode is specified via cli\n\n[client]\nremote_addr = \"localhost:2333\"\ndefault_token = \"123\"\n\n[client.services.foo1]\nlocal_addr = \"127.0.0.1:80\"\n\n[server]\nbind_addr = \"0.0.0.0:2333\"\ndefault_token = \"123\"\n\n[server.services.foo1]\nbind_addr = \"0.0.0.0:5202\"\n"
  },
  {
    "path": "examples/use_proxy/client.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\"\ndefault_token = \"123\"\n\n[client.services.foo1]\nlocal_addr = \"127.0.0.1:80\"\n\n[client.transport]\ntype = \"tcp\"\n[client.transport.tcp]\n# `proxy` controls how the client connect to the server\n# Use socks5 proxy at 127.0.0.1, with port 1080, username 'myuser' and password 'mypass'\nproxy = \"socks5://myuser:mypass@127.0.0.1:1080\"\n# Use http proxy. Similar to socks5 proxy\n# proxy = \"http://myuser:mypass@127.0.0.1:8080\"\n"
  },
  {
    "path": "rust-toolchain",
    "content": "1.71.0"
  },
  {
    "path": "src/cli.rs",
    "content": "use clap::{AppSettings, ArgGroup, Parser};\nuse lazy_static::lazy_static;\n\n#[derive(clap::ArgEnum, Clone, Debug, Copy)]\npub enum KeypairType {\n    X25519,\n    X448,\n}\n\nlazy_static! {\n    static ref VERSION: &'static str =\n        option_env!(\"VERGEN_GIT_SEMVER_LIGHTWEIGHT\").unwrap_or(env!(\"VERGEN_BUILD_SEMVER\"));\n    static ref LONG_VERSION: String = format!(\n        \"\nBuild Timestamp:     {}\nBuild Version:       {}\nCommit SHA:          {:?}\nCommit Date:         {:?}\nCommit Branch:       {:?}\ncargo Target Triple: {}\ncargo Profile:       {}\ncargo Features:      {}\n\",\n        env!(\"VERGEN_BUILD_TIMESTAMP\"),\n        env!(\"VERGEN_BUILD_SEMVER\"),\n        option_env!(\"VERGEN_GIT_SHA\"),\n        option_env!(\"VERGEN_GIT_COMMIT_TIMESTAMP\"),\n        option_env!(\"VERGEN_GIT_BRANCH\"),\n        env!(\"VERGEN_CARGO_TARGET_TRIPLE\"),\n        env!(\"VERGEN_CARGO_PROFILE\"),\n        env!(\"VERGEN_CARGO_FEATURES\")\n    );\n}\n\n#[derive(Parser, Debug, Default, Clone)]\n#[clap(\n    about,\n    version(*VERSION),\n    long_version(LONG_VERSION.as_str()),\n    setting(AppSettings::DeriveDisplayOrder)\n)]\n#[clap(group(\n            ArgGroup::new(\"cmds\")\n                .required(true)\n                .args(&[\"CONFIG\", \"genkey\"]),\n        ))]\npub struct Cli {\n    /// The path to the configuration file\n    ///\n    /// Running as a client or a server is automatically determined\n    /// according to the configuration file.\n    #[clap(parse(from_os_str), name = \"CONFIG\")]\n    pub config_path: Option<std::path::PathBuf>,\n\n    /// Run as a server\n    #[clap(long, short, group = \"mode\")]\n    pub server: bool,\n\n    /// Run as a client\n    #[clap(long, short, group = \"mode\")]\n    pub client: bool,\n\n    /// Generate a keypair for the use of the noise protocol\n    ///\n    /// The DH function to use is x25519\n    #[clap(long, arg_enum, value_name = \"CURVE\")]\n    pub genkey: Option<Option<KeypairType>>,\n}\n"
  },
  {
    "path": "src/client.rs",
    "content": "use crate::config::{ClientConfig, ClientServiceConfig, Config, ServiceType, TransportType};\nuse crate::config_watcher::{ClientServiceChange, ConfigChange};\nuse crate::helper::udp_connect;\nuse crate::protocol::Hello::{self, *};\nuse crate::protocol::{\n    self, read_ack, read_control_cmd, read_data_cmd, read_hello, Ack, Auth, ControlChannelCmd,\n    DataChannelCmd, UdpTraffic, CURRENT_PROTO_VERSION, HASH_WIDTH_IN_BYTES,\n};\nuse crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};\nuse anyhow::{anyhow, bail, Context, Result};\nuse backoff::backoff::Backoff;\nuse backoff::future::retry_notify;\nuse backoff::ExponentialBackoff;\nuse bytes::{Bytes, BytesMut};\nuse std::collections::HashMap;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse tokio::io::{self, copy_bidirectional, AsyncReadExt, AsyncWriteExt};\nuse tokio::net::{TcpStream, UdpSocket};\nuse tokio::sync::{broadcast, mpsc, oneshot, RwLock};\nuse tokio::time::{self, Duration, Instant};\nuse tracing::{debug, error, info, instrument, trace, warn, Instrument, Span};\n\n#[cfg(feature = \"noise\")]\nuse crate::transport::NoiseTransport;\n#[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\nuse crate::transport::TlsTransport;\n#[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\nuse crate::transport::WebsocketTransport;\n\nuse crate::constants::{run_control_chan_backoff, UDP_BUFFER_SIZE, UDP_SENDQ_SIZE, UDP_TIMEOUT};\n\n// The entrypoint of running a client\npub async fn run_client(\n    config: Config,\n    shutdown_rx: broadcast::Receiver<bool>,\n    update_rx: mpsc::Receiver<ConfigChange>,\n) -> Result<()> {\n    let config = config.client.ok_or_else(|| {\n        anyhow!(\n        \"Try to run as a client, but the configuration is missing. Please add the `[client]` block\"\n    )\n    })?;\n\n    match config.transport.transport_type {\n        TransportType::Tcp => {\n            let mut client = Client::<TcpTransport>::from(config).await?;\n            client.run(shutdown_rx, update_rx).await\n        }\n        TransportType::Tls => {\n            #[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\n            {\n                let mut client = Client::<TlsTransport>::from(config).await?;\n                client.run(shutdown_rx, update_rx).await\n            }\n            #[cfg(not(any(feature = \"native-tls\", feature = \"rustls\")))]\n            crate::helper::feature_neither_compile(\"native-tls\", \"rustls\")\n        }\n        TransportType::Noise => {\n            #[cfg(feature = \"noise\")]\n            {\n                let mut client = Client::<NoiseTransport>::from(config).await?;\n                client.run(shutdown_rx, update_rx).await\n            }\n            #[cfg(not(feature = \"noise\"))]\n            crate::helper::feature_not_compile(\"noise\")\n        }\n        TransportType::Websocket => {\n            #[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\n            {\n                let mut client = Client::<WebsocketTransport>::from(config).await?;\n                client.run(shutdown_rx, update_rx).await\n            }\n            #[cfg(not(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\")))]\n            crate::helper::feature_neither_compile(\"websocket-native-tls\", \"websocket-rustls\")\n        }\n    }\n}\n\ntype ServiceDigest = protocol::Digest;\ntype Nonce = protocol::Digest;\n\n// Holds the state of a client\nstruct Client<T: Transport> {\n    config: ClientConfig,\n    service_handles: HashMap<String, ControlChannelHandle>,\n    transport: Arc<T>,\n}\n\nimpl<T: 'static + Transport> Client<T> {\n    // Create a Client from `[client]` config block\n    async fn from(config: ClientConfig) -> Result<Client<T>> {\n        let transport =\n            Arc::new(T::new(&config.transport).with_context(|| \"Failed to create the transport\")?);\n        Ok(Client {\n            config,\n            service_handles: HashMap::new(),\n            transport,\n        })\n    }\n\n    // The entrypoint of Client\n    async fn run(\n        &mut self,\n        mut shutdown_rx: broadcast::Receiver<bool>,\n        mut update_rx: mpsc::Receiver<ConfigChange>,\n    ) -> Result<()> {\n        for (name, config) in &self.config.services {\n            // Create a control channel for each service defined\n            let handle = ControlChannelHandle::new(\n                (*config).clone(),\n                self.config.remote_addr.clone(),\n                self.transport.clone(),\n                self.config.heartbeat_timeout,\n            );\n            self.service_handles.insert(name.clone(), handle);\n        }\n\n        // Wait for the shutdown signal\n        loop {\n            tokio::select! {\n                val = shutdown_rx.recv() => {\n                    match val {\n                        Ok(_) => {}\n                        Err(err) => {\n                            error!(\"Unable to listen for shutdown signal: {}\", err);\n                        }\n                    }\n                    break;\n                },\n                e = update_rx.recv() => {\n                    if let Some(e) = e {\n                        self.handle_hot_reload(e).await;\n                    }\n                }\n            }\n        }\n\n        // Shutdown all services\n        for (_, handle) in self.service_handles.drain() {\n            handle.shutdown();\n        }\n\n        Ok(())\n    }\n\n    async fn handle_hot_reload(&mut self, e: ConfigChange) {\n        match e {\n            ConfigChange::ClientChange(client_change) => match client_change {\n                ClientServiceChange::Add(cfg) => {\n                    let name = cfg.name.clone();\n                    let handle = ControlChannelHandle::new(\n                        cfg,\n                        self.config.remote_addr.clone(),\n                        self.transport.clone(),\n                        self.config.heartbeat_timeout,\n                    );\n                    let _ = self.service_handles.insert(name, handle);\n                }\n                ClientServiceChange::Delete(s) => {\n                    let _ = self.service_handles.remove(&s);\n                }\n            },\n            ignored => warn!(\"Ignored {:?} since running as a client\", ignored),\n        }\n    }\n}\n\nstruct RunDataChannelArgs<T: Transport> {\n    session_key: Nonce,\n    remote_addr: AddrMaybeCached,\n    connector: Arc<T>,\n    socket_opts: SocketOpts,\n    service: ClientServiceConfig,\n}\n\nasync fn do_data_channel_handshake<T: Transport>(\n    args: Arc<RunDataChannelArgs<T>>,\n) -> Result<T::Stream> {\n    // Retry at least every 100ms, at most for 10 seconds\n    let backoff = ExponentialBackoff {\n        max_interval: Duration::from_millis(100),\n        max_elapsed_time: Some(Duration::from_secs(10)),\n        ..Default::default()\n    };\n\n    // Connect to remote_addr\n    let mut conn: T::Stream = retry_notify(\n        backoff,\n        || async {\n            args.connector\n                .connect(&args.remote_addr)\n                .await\n                .with_context(|| format!(\"Failed to connect to {}\", &args.remote_addr))\n                .map_err(backoff::Error::transient)\n        },\n        |e, duration| {\n            warn!(\"{:#}. Retry in {:?}\", e, duration);\n        },\n    )\n    .await?;\n\n    T::hint(&conn, args.socket_opts);\n\n    // Send nonce\n    let v: &[u8; HASH_WIDTH_IN_BYTES] = args.session_key[..].try_into().unwrap();\n    let hello = Hello::DataChannelHello(CURRENT_PROTO_VERSION, v.to_owned());\n    conn.write_all(&bincode::serialize(&hello).unwrap()).await?;\n    conn.flush().await?;\n\n    Ok(conn)\n}\n\nasync fn run_data_channel<T: Transport>(args: Arc<RunDataChannelArgs<T>>) -> Result<()> {\n    // Do the handshake\n    let mut conn = do_data_channel_handshake(args.clone()).await?;\n\n    // Forward\n    match read_data_cmd(&mut conn).await? {\n        DataChannelCmd::StartForwardTcp => {\n            if args.service.service_type != ServiceType::Tcp {\n                bail!(\"Expect TCP traffic. Please check the configuration.\")\n            }\n            run_data_channel_for_tcp::<T>(conn, &args.service.local_addr).await?;\n        }\n        DataChannelCmd::StartForwardUdp => {\n            if args.service.service_type != ServiceType::Udp {\n                bail!(\"Expect UDP traffic. Please check the configuration.\")\n            }\n            run_data_channel_for_udp::<T>(conn, &args.service.local_addr, args.service.prefer_ipv6).await?;\n        }\n    }\n    Ok(())\n}\n\n// Simply copying back and forth for TCP\n#[instrument(skip(conn))]\nasync fn run_data_channel_for_tcp<T: Transport>(\n    mut conn: T::Stream,\n    local_addr: &str,\n) -> Result<()> {\n    debug!(\"New data channel starts forwarding\");\n\n    let mut local = TcpStream::connect(local_addr)\n        .await\n        .with_context(|| format!(\"Failed to connect to {}\", local_addr))?;\n    let _ = copy_bidirectional(&mut conn, &mut local).await;\n    Ok(())\n}\n\n// Things get a little tricker when it gets to UDP because it's connection-less.\n// A UdpPortMap must be maintained for recent seen incoming address, giving them\n// each a local port, which is associated with a socket. So just the sender\n// to the socket will work fine for the map's value.\ntype UdpPortMap = Arc<RwLock<HashMap<SocketAddr, mpsc::Sender<Bytes>>>>;\n\n#[instrument(skip(conn))]\nasync fn run_data_channel_for_udp<T: Transport>(conn: T::Stream, local_addr: &str, prefer_ipv6: bool) -> Result<()> {\n    debug!(\"New data channel starts forwarding\");\n\n    let port_map: UdpPortMap = Arc::new(RwLock::new(HashMap::new()));\n\n    // The channel stores UdpTraffic that needs to be sent to the server\n    let (outbound_tx, mut outbound_rx) = mpsc::channel::<UdpTraffic>(UDP_SENDQ_SIZE);\n\n    // FIXME: https://github.com/tokio-rs/tls/issues/40\n    // Maybe this is our concern\n    let (mut rd, mut wr) = io::split(conn);\n\n    // Keep sending items from the outbound channel to the server\n    tokio::spawn(async move {\n        while let Some(t) = outbound_rx.recv().await {\n            trace!(\"outbound {:?}\", t);\n            if let Err(e) = t\n                .write(&mut wr)\n                .await\n                .with_context(|| \"Failed to forward UDP traffic to the server\")\n            {\n                debug!(\"{:?}\", e);\n                break;\n            }\n        }\n    });\n\n    loop {\n        // Read a packet from the server\n        let hdr_len = rd.read_u8().await?;\n        let packet = UdpTraffic::read(&mut rd, hdr_len)\n            .await\n            .with_context(|| \"Failed to read UDPTraffic from the server\")?;\n        let m = port_map.read().await;\n\n        if m.get(&packet.from).is_none() {\n            // This packet is from a address we don't see for a while,\n            // which is not in the UdpPortMap.\n            // So set up a mapping (and a forwarder) for it\n\n            // Drop the reader lock\n            drop(m);\n\n            // Grab the writer lock\n            // This is the only thread that will try to grab the writer lock\n            // So no need to worry about some other thread has already set up\n            // the mapping between the gap of dropping the reader lock and\n            // grabbing the writer lock\n            let mut m = port_map.write().await;\n\n            match udp_connect(local_addr, prefer_ipv6).await {\n                Ok(s) => {\n                    let (inbound_tx, inbound_rx) = mpsc::channel(UDP_SENDQ_SIZE);\n                    m.insert(packet.from, inbound_tx);\n                    tokio::spawn(run_udp_forwarder(\n                        s,\n                        inbound_rx,\n                        outbound_tx.clone(),\n                        packet.from,\n                        port_map.clone(),\n                    ));\n                }\n                Err(e) => {\n                    error!(\"{:#}\", e);\n                }\n            }\n        }\n\n        // Now there should be a udp forwarder that can receive the packet\n        let m = port_map.read().await;\n        if let Some(tx) = m.get(&packet.from) {\n            let _ = tx.send(packet.data).await;\n        }\n    }\n}\n\n// Run a UdpSocket for the visitor `from`\n#[instrument(skip_all, fields(from))]\nasync fn run_udp_forwarder(\n    s: UdpSocket,\n    mut inbound_rx: mpsc::Receiver<Bytes>,\n    outbount_tx: mpsc::Sender<UdpTraffic>,\n    from: SocketAddr,\n    port_map: UdpPortMap,\n) -> Result<()> {\n    debug!(\"Forwarder created\");\n    let mut buf = BytesMut::new();\n    buf.resize(UDP_BUFFER_SIZE, 0);\n\n    loop {\n        tokio::select! {\n            // Receive from the server\n            data = inbound_rx.recv() => {\n                if let Some(data) = data {\n                    s.send(&data).await?;\n                } else {\n                    break;\n                }\n            },\n\n            // Receive from the service\n            val = s.recv(&mut buf) => {\n                let len = match val {\n                    Ok(v) => v,\n                    Err(_) => break\n                };\n\n                let t = UdpTraffic{\n                    from,\n                    data: Bytes::copy_from_slice(&buf[..len])\n                };\n\n                outbount_tx.send(t).await?;\n            },\n\n            // No traffic for the duration of UDP_TIMEOUT, clean up the state\n            _ = time::sleep(Duration::from_secs(UDP_TIMEOUT)) => {\n                break;\n            }\n        }\n    }\n\n    let mut port_map = port_map.write().await;\n    port_map.remove(&from);\n\n    debug!(\"Forwarder dropped\");\n    Ok(())\n}\n\n// Control channel, using T as the transport layer\nstruct ControlChannel<T: Transport> {\n    digest: ServiceDigest,              // SHA256 of the service name\n    service: ClientServiceConfig,       // `[client.services.foo]` config block\n    shutdown_rx: oneshot::Receiver<u8>, // Receives the shutdown signal\n    remote_addr: String,                // `client.remote_addr`\n    transport: Arc<T>,                  // Wrapper around the transport layer\n    heartbeat_timeout: u64,             // Application layer heartbeat timeout in secs\n}\n\n// Handle of a control channel\n// Dropping it will also drop the actual control channel\nstruct ControlChannelHandle {\n    shutdown_tx: oneshot::Sender<u8>,\n}\n\nimpl<T: 'static + Transport> ControlChannel<T> {\n    #[instrument(skip_all)]\n    async fn run(&mut self) -> Result<()> {\n        let mut remote_addr = AddrMaybeCached::new(&self.remote_addr);\n        remote_addr.resolve().await?;\n\n        let mut conn = self\n            .transport\n            .connect(&remote_addr)\n            .await\n            .with_context(|| format!(\"Failed to connect to {}\", &self.remote_addr))?;\n        T::hint(&conn, SocketOpts::for_control_channel());\n\n        // Send hello\n        debug!(\"Sending hello\");\n        let hello_send =\n            Hello::ControlChannelHello(CURRENT_PROTO_VERSION, self.digest[..].try_into().unwrap());\n        conn.write_all(&bincode::serialize(&hello_send).unwrap())\n            .await?;\n        conn.flush().await?;\n\n        // Read hello\n        debug!(\"Reading hello\");\n        let nonce = match read_hello(&mut conn).await? {\n            ControlChannelHello(_, d) => d,\n            _ => {\n                bail!(\"Unexpected type of hello\");\n            }\n        };\n\n        // Send auth\n        debug!(\"Sending auth\");\n        let mut concat = Vec::from(self.service.token.as_ref().unwrap().as_bytes());\n        concat.extend_from_slice(&nonce);\n\n        let session_key = protocol::digest(&concat);\n        let auth = Auth(session_key);\n        conn.write_all(&bincode::serialize(&auth).unwrap()).await?;\n        conn.flush().await?;\n\n        // Read ack\n        debug!(\"Reading ack\");\n        match read_ack(&mut conn).await? {\n            Ack::Ok => {}\n            v => {\n                return Err(anyhow!(\"{}\", v))\n                    .with_context(|| format!(\"Authentication failed: {}\", self.service.name));\n            }\n        }\n\n        // Channel ready\n        info!(\"Control channel established\");\n\n        // Socket options for the data channel\n        let socket_opts = SocketOpts::from_client_cfg(&self.service);\n        let data_ch_args = Arc::new(RunDataChannelArgs {\n            session_key,\n            remote_addr,\n            connector: self.transport.clone(),\n            socket_opts,\n            service: self.service.clone(),\n        });\n\n        loop {\n            tokio::select! {\n                val = read_control_cmd(&mut conn) => {\n                    let val = val?;\n                    debug!( \"Received {:?}\", val);\n                    match val {\n                        ControlChannelCmd::CreateDataChannel => {\n                            let args = data_ch_args.clone();\n                            tokio::spawn(async move {\n                                if let Err(e) = run_data_channel(args).await.with_context(|| \"Failed to run the data channel\") {\n                                    warn!(\"{:#}\", e);\n                                }\n                            }.instrument(Span::current()));\n                        },\n                        ControlChannelCmd::HeartBeat => ()\n                    }\n                },\n                _ = time::sleep(Duration::from_secs(self.heartbeat_timeout)), if self.heartbeat_timeout != 0 => {\n                    return Err(anyhow!(\"Heartbeat timed out\"))\n                }\n                _ = &mut self.shutdown_rx => {\n                    break;\n                }\n            }\n        }\n\n        info!(\"Control channel shutdown\");\n        Ok(())\n    }\n}\n\nimpl ControlChannelHandle {\n    #[instrument(name=\"handle\", skip_all, fields(service = %service.name))]\n    fn new<T: 'static + Transport>(\n        service: ClientServiceConfig,\n        remote_addr: String,\n        transport: Arc<T>,\n        heartbeat_timeout: u64,\n    ) -> ControlChannelHandle {\n        let digest = protocol::digest(service.name.as_bytes());\n\n        info!(\"Starting {}\", hex::encode(digest));\n        let (shutdown_tx, shutdown_rx) = oneshot::channel();\n\n        let mut retry_backoff = run_control_chan_backoff(service.retry_interval.unwrap());\n\n        let mut s = ControlChannel {\n            digest,\n            service,\n            shutdown_rx,\n            remote_addr,\n            transport,\n            heartbeat_timeout,\n        };\n\n        tokio::spawn(\n            async move {\n                let mut start = Instant::now();\n\n                while let Err(err) = s\n                    .run()\n                    .await\n                    .with_context(|| \"Failed to run the control channel\")\n                {\n                    if s.shutdown_rx.try_recv() != Err(oneshot::error::TryRecvError::Empty) {\n                        break;\n                    }\n\n                    if start.elapsed() > Duration::from_secs(3) {\n                        // The client runs for at least 3 secs and then disconnects\n                        retry_backoff.reset();\n                    }\n\n                    if let Some(duration) = retry_backoff.next_backoff() {\n                        error!(\"{:#}. Retry in {:?}...\", err, duration);\n                        time::sleep(duration).await;\n                    } else {\n                        // Should never reach\n                        panic!(\"{:#}. Break\", err);\n                    }\n\n                    start = Instant::now();\n                }\n            }\n            .instrument(Span::current()),\n        );\n\n        ControlChannelHandle { shutdown_tx }\n    }\n\n    fn shutdown(self) {\n        // A send failure shows that the actor has already shutdown.\n        let _ = self.shutdown_tx.send(0u8);\n    }\n}\n"
  },
  {
    "path": "src/config.rs",
    "content": "use anyhow::{anyhow, bail, Context, Result};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::fmt::{Debug, Formatter};\nuse std::ops::Deref;\nuse std::path::Path;\nuse tokio::fs;\nuse url::Url;\n\nuse crate::transport::{DEFAULT_KEEPALIVE_INTERVAL, DEFAULT_KEEPALIVE_SECS, DEFAULT_NODELAY};\n\n/// Application-layer heartbeat interval in secs\nconst DEFAULT_HEARTBEAT_INTERVAL_SECS: u64 = 30;\nconst DEFAULT_HEARTBEAT_TIMEOUT_SECS: u64 = 40;\n\n/// Client\nconst DEFAULT_CLIENT_RETRY_INTERVAL_SECS: u64 = 1;\n\n/// String with Debug implementation that emits \"MASKED\"\n/// Used to mask sensitive strings when logging\n#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone)]\npub struct MaskedString(String);\n\nimpl Debug for MaskedString {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        f.write_str(\"MASKED\")\n    }\n}\n\nimpl Deref for MaskedString {\n    type Target = str;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl From<&str> for MaskedString {\n    fn from(s: &str) -> MaskedString {\n        MaskedString(String::from(s))\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Default)]\npub enum TransportType {\n    #[default]\n    #[serde(rename = \"tcp\")]\n    Tcp,\n    #[serde(rename = \"tls\")]\n    Tls,\n    #[serde(rename = \"noise\")]\n    Noise,\n    #[serde(rename = \"websocket\")]\n    Websocket,\n}\n\n/// Per service config\n/// All Option are optional in configuration but must be Some value in runtime\n#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]\n#[serde(deny_unknown_fields)]\npub struct ClientServiceConfig {\n    #[serde(rename = \"type\", default = \"default_service_type\")]\n    pub service_type: ServiceType,\n    #[serde(skip)]\n    pub name: String,\n    pub local_addr: String,\n    #[serde(default)] // Default to false\n    pub prefer_ipv6: bool,\n    pub token: Option<MaskedString>,\n    pub nodelay: Option<bool>,\n    pub retry_interval: Option<u64>,\n}\n\nimpl ClientServiceConfig {\n    pub fn with_name(name: &str) -> ClientServiceConfig {\n        ClientServiceConfig {\n            name: name.to_string(),\n            ..Default::default()\n        }\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]\npub enum ServiceType {\n    #[serde(rename = \"tcp\")]\n    #[default]\n    Tcp,\n    #[serde(rename = \"udp\")]\n    Udp,\n}\n\nfn default_service_type() -> ServiceType {\n    Default::default()\n}\n\n/// Per service config\n/// All Option are optional in configuration but must be Some value in runtime\n#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]\n#[serde(deny_unknown_fields)]\npub struct ServerServiceConfig {\n    #[serde(rename = \"type\", default = \"default_service_type\")]\n    pub service_type: ServiceType,\n    #[serde(skip)]\n    pub name: String,\n    pub bind_addr: String,\n    pub token: Option<MaskedString>,\n    pub nodelay: Option<bool>,\n}\n\nimpl ServerServiceConfig {\n    pub fn with_name(name: &str) -> ServerServiceConfig {\n        ServerServiceConfig {\n            name: name.to_string(),\n            ..Default::default()\n        }\n    }\n}\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]\n#[serde(deny_unknown_fields)]\npub struct TlsConfig {\n    pub hostname: Option<String>,\n    pub trusted_root: Option<String>,\n    pub pkcs12: Option<String>,\n    pub pkcs12_password: Option<MaskedString>,\n}\n\nfn default_noise_pattern() -> String {\n    String::from(\"Noise_NK_25519_ChaChaPoly_BLAKE2s\")\n}\n\n#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]\n#[serde(deny_unknown_fields)]\npub struct NoiseConfig {\n    #[serde(default = \"default_noise_pattern\")]\n    pub pattern: String,\n    pub local_private_key: Option<MaskedString>,\n    pub remote_public_key: Option<String>,\n    // TODO: Maybe psk can be added\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]\n#[serde(deny_unknown_fields)]\npub struct WebsocketConfig {\n    pub tls: bool,\n}\n\nfn default_nodelay() -> bool {\n    DEFAULT_NODELAY\n}\n\nfn default_keepalive_secs() -> u64 {\n    DEFAULT_KEEPALIVE_SECS\n}\n\nfn default_keepalive_interval() -> u64 {\n    DEFAULT_KEEPALIVE_INTERVAL\n}\n\n#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]\n#[serde(deny_unknown_fields)]\npub struct TcpConfig {\n    #[serde(default = \"default_nodelay\")]\n    pub nodelay: bool,\n    #[serde(default = \"default_keepalive_secs\")]\n    pub keepalive_secs: u64,\n    #[serde(default = \"default_keepalive_interval\")]\n    pub keepalive_interval: u64,\n    pub proxy: Option<Url>,\n}\n\nimpl Default for TcpConfig {\n    fn default() -> Self {\n        Self {\n            nodelay: default_nodelay(),\n            keepalive_secs: default_keepalive_secs(),\n            keepalive_interval: default_keepalive_interval(),\n            proxy: None,\n        }\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]\n#[serde(deny_unknown_fields)]\npub struct TransportConfig {\n    #[serde(rename = \"type\")]\n    pub transport_type: TransportType,\n    #[serde(default)]\n    pub tcp: TcpConfig,\n    pub tls: Option<TlsConfig>,\n    pub noise: Option<NoiseConfig>,\n    pub websocket: Option<WebsocketConfig>,\n}\n\nfn default_heartbeat_timeout() -> u64 {\n    DEFAULT_HEARTBEAT_TIMEOUT_SECS\n}\n\nfn default_client_retry_interval() -> u64 {\n    DEFAULT_CLIENT_RETRY_INTERVAL_SECS\n}\n\n#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)]\n#[serde(deny_unknown_fields)]\npub struct ClientConfig {\n    pub remote_addr: String,\n    pub default_token: Option<MaskedString>,\n    pub prefer_ipv6: Option<bool>,\n    pub services: HashMap<String, ClientServiceConfig>,\n    #[serde(default)]\n    pub transport: TransportConfig,\n    #[serde(default = \"default_heartbeat_timeout\")]\n    pub heartbeat_timeout: u64,\n    #[serde(default = \"default_client_retry_interval\")]\n    pub retry_interval: u64,\n}\n\nfn default_heartbeat_interval() -> u64 {\n    DEFAULT_HEARTBEAT_INTERVAL_SECS\n}\n\n#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)]\n#[serde(deny_unknown_fields)]\npub struct ServerConfig {\n    pub bind_addr: String,\n    pub default_token: Option<MaskedString>,\n    pub services: HashMap<String, ServerServiceConfig>,\n    #[serde(default)]\n    pub transport: TransportConfig,\n    #[serde(default = \"default_heartbeat_interval\")]\n    pub heartbeat_interval: u64,\n}\n\n#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]\n#[serde(deny_unknown_fields)]\npub struct Config {\n    pub server: Option<ServerConfig>,\n    pub client: Option<ClientConfig>,\n}\n\nimpl Config {\n    fn from_str(s: &str) -> Result<Config> {\n        let mut config: Config = toml::from_str(s).with_context(|| \"Failed to parse the config\")?;\n\n        if let Some(server) = config.server.as_mut() {\n            Config::validate_server_config(server)?;\n        }\n\n        if let Some(client) = config.client.as_mut() {\n            Config::validate_client_config(client)?;\n        }\n\n        if config.server.is_none() && config.client.is_none() {\n            Err(anyhow!(\"Neither of `[server]` or `[client]` is defined\"))\n        } else {\n            Ok(config)\n        }\n    }\n\n    fn validate_server_config(server: &mut ServerConfig) -> Result<()> {\n        // Validate services\n        for (name, s) in &mut server.services {\n            s.name = name.clone();\n            if s.token.is_none() {\n                s.token = server.default_token.clone();\n                if s.token.is_none() {\n                    bail!(\"The token of service {} is not set\", name);\n                }\n            }\n        }\n\n        Config::validate_transport_config(&server.transport, true)?;\n\n        Ok(())\n    }\n\n    fn validate_client_config(client: &mut ClientConfig) -> Result<()> {\n        // Validate services\n        for (name, s) in &mut client.services {\n            s.name = name.clone();\n            if s.token.is_none() {\n                s.token = client.default_token.clone();\n                if s.token.is_none() {\n                    bail!(\"The token of service {} is not set\", name);\n                }\n            }\n            if s.retry_interval.is_none() {\n                s.retry_interval = Some(client.retry_interval);\n            }\n        }\n\n        Config::validate_transport_config(&client.transport, false)?;\n\n        Ok(())\n    }\n\n    fn validate_transport_config(config: &TransportConfig, is_server: bool) -> Result<()> {\n        config\n            .tcp\n            .proxy\n            .as_ref()\n            .map_or(Ok(()), |u| match u.scheme() {\n                \"socks5\" => Ok(()),\n                \"http\" => Ok(()),\n                _ => Err(anyhow!(format!(\"Unknown proxy scheme: {}\", u.scheme()))),\n            })?;\n        match config.transport_type {\n            TransportType::Tcp => Ok(()),\n            TransportType::Tls => {\n                let tls_config = config\n                    .tls\n                    .as_ref()\n                    .ok_or_else(|| anyhow!(\"Missing TLS configuration\"))?;\n                if is_server {\n                    tls_config\n                        .pkcs12\n                        .as_ref()\n                        .and(tls_config.pkcs12_password.as_ref())\n                        .ok_or_else(|| anyhow!(\"Missing `pkcs12` or `pkcs12_password`\"))?;\n                }\n                Ok(())\n            }\n            TransportType::Noise => {\n                // The check is done in transport\n                Ok(())\n            }\n            TransportType::Websocket => Ok(()),\n        }\n    }\n\n    pub async fn from_file(path: &Path) -> Result<Config> {\n        let s: String = fs::read_to_string(path)\n            .await\n            .with_context(|| format!(\"Failed to read the config {:?}\", path))?;\n        Config::from_str(&s).with_context(|| {\n            \"Configuration is invalid. Please refer to the configuration specification.\"\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::{fs, path::PathBuf};\n\n    use anyhow::Result;\n\n    fn list_config_files<T: AsRef<Path>>(root: T) -> Result<Vec<PathBuf>> {\n        let mut files = Vec::new();\n        for entry in fs::read_dir(root)? {\n            let entry = entry?;\n            let path = entry.path();\n            if path.is_file() {\n                files.push(path);\n            } else if path.is_dir() {\n                files.append(&mut list_config_files(path)?);\n            }\n        }\n        Ok(files)\n    }\n\n    fn get_all_example_config() -> Result<Vec<PathBuf>> {\n        Ok(list_config_files(\"./examples\")?\n            .into_iter()\n            .filter(|x| x.ends_with(\".toml\"))\n            .collect())\n    }\n\n    #[test]\n    fn test_example_config() -> Result<()> {\n        let paths = get_all_example_config()?;\n        for p in paths {\n            let s = fs::read_to_string(p)?;\n            Config::from_str(&s)?;\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn test_valid_config() -> Result<()> {\n        let paths = list_config_files(\"tests/config_test/valid_config\")?;\n        for p in paths {\n            let s = fs::read_to_string(p)?;\n            Config::from_str(&s)?;\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn test_invalid_config() -> Result<()> {\n        let paths = list_config_files(\"tests/config_test/invalid_config\")?;\n        for p in paths {\n            let s = fs::read_to_string(p)?;\n            assert!(Config::from_str(&s).is_err());\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn test_validate_server_config() -> Result<()> {\n        let mut cfg = ServerConfig::default();\n\n        cfg.services.insert(\n            \"foo1\".into(),\n            ServerServiceConfig {\n                service_type: ServiceType::Tcp,\n                name: \"foo1\".into(),\n                bind_addr: \"127.0.0.1:80\".into(),\n                token: None,\n                ..Default::default()\n            },\n        );\n\n        // Missing the token\n        assert!(Config::validate_server_config(&mut cfg).is_err());\n\n        // Use the default token\n        cfg.default_token = Some(\"123\".into());\n        assert!(Config::validate_server_config(&mut cfg).is_ok());\n        assert_eq!(\n            cfg.services\n                .get(\"foo1\")\n                .as_ref()\n                .unwrap()\n                .token\n                .as_ref()\n                .unwrap()\n                .0,\n            \"123\"\n        );\n\n        // The default token won't override the service token\n        cfg.services.get_mut(\"foo1\").unwrap().token = Some(\"4\".into());\n        assert!(Config::validate_server_config(&mut cfg).is_ok());\n        assert_eq!(\n            cfg.services\n                .get(\"foo1\")\n                .as_ref()\n                .unwrap()\n                .token\n                .as_ref()\n                .unwrap()\n                .0,\n            \"4\"\n        );\n        Ok(())\n    }\n\n    #[test]\n    fn test_validate_client_config() -> Result<()> {\n        let mut cfg = ClientConfig::default();\n\n        cfg.services.insert(\n            \"foo1\".into(),\n            ClientServiceConfig {\n                service_type: ServiceType::Tcp,\n                name: \"foo1\".into(),\n                local_addr: \"127.0.0.1:80\".into(),\n                token: None,\n                ..Default::default()\n            },\n        );\n\n        // Missing the token\n        assert!(Config::validate_client_config(&mut cfg).is_err());\n\n        // Use the default token\n        cfg.default_token = Some(\"123\".into());\n        assert!(Config::validate_client_config(&mut cfg).is_ok());\n        assert_eq!(\n            cfg.services\n                .get(\"foo1\")\n                .as_ref()\n                .unwrap()\n                .token\n                .as_ref()\n                .unwrap()\n                .0,\n            \"123\"\n        );\n\n        // The default token won't override the service token\n        cfg.services.get_mut(\"foo1\").unwrap().token = Some(\"4\".into());\n        assert!(Config::validate_client_config(&mut cfg).is_ok());\n        assert_eq!(\n            cfg.services\n                .get(\"foo1\")\n                .as_ref()\n                .unwrap()\n                .token\n                .as_ref()\n                .unwrap()\n                .0,\n            \"4\"\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/config_watcher.rs",
    "content": "use crate::{\n    config::{ClientConfig, ClientServiceConfig, ServerConfig, ServerServiceConfig},\n    Config,\n};\nuse anyhow::{Context, Result};\nuse std::{\n    collections::HashMap,\n    env,\n    path::{Path, PathBuf},\n};\nuse tokio::sync::{broadcast, mpsc};\nuse tracing::{error, info, instrument};\n\n#[cfg(feature = \"notify\")]\nuse notify::{EventKind, RecursiveMode, Watcher};\n\n#[derive(Debug, PartialEq, Eq, Clone)]\npub enum ConfigChange {\n    General(Box<Config>), // Trigger a full restart\n    ServerChange(ServerServiceChange),\n    ClientChange(ClientServiceChange),\n}\n\n#[derive(Debug, PartialEq, Eq, Clone)]\npub enum ClientServiceChange {\n    Add(ClientServiceConfig),\n    Delete(String),\n}\n\n#[derive(Debug, PartialEq, Eq, Clone)]\npub enum ServerServiceChange {\n    Add(ServerServiceConfig),\n    Delete(String),\n}\n\ntrait InstanceConfig: Clone {\n    type ServiceConfig: PartialEq + Eq + Clone;\n    fn equal_without_service(&self, rhs: &Self) -> bool;\n    fn service_delete_change(s: String) -> ConfigChange;\n    fn service_add_change(cfg: Self::ServiceConfig) -> ConfigChange;\n    fn get_services(&self) -> &HashMap<String, Self::ServiceConfig>;\n}\n\nimpl InstanceConfig for ServerConfig {\n    type ServiceConfig = ServerServiceConfig;\n    fn equal_without_service(&self, rhs: &Self) -> bool {\n        let left = ServerConfig {\n            services: Default::default(),\n            ..self.clone()\n        };\n\n        let right = ServerConfig {\n            services: Default::default(),\n            ..rhs.clone()\n        };\n\n        left == right\n    }\n    fn service_delete_change(s: String) -> ConfigChange {\n        ConfigChange::ServerChange(ServerServiceChange::Delete(s))\n    }\n    fn service_add_change(cfg: Self::ServiceConfig) -> ConfigChange {\n        ConfigChange::ServerChange(ServerServiceChange::Add(cfg))\n    }\n    fn get_services(&self) -> &HashMap<String, Self::ServiceConfig> {\n        &self.services\n    }\n}\n\nimpl InstanceConfig for ClientConfig {\n    type ServiceConfig = ClientServiceConfig;\n    fn equal_without_service(&self, rhs: &Self) -> bool {\n        let left = ClientConfig {\n            services: Default::default(),\n            ..self.clone()\n        };\n\n        let right = ClientConfig {\n            services: Default::default(),\n            ..rhs.clone()\n        };\n\n        left == right\n    }\n    fn service_delete_change(s: String) -> ConfigChange {\n        ConfigChange::ClientChange(ClientServiceChange::Delete(s))\n    }\n    fn service_add_change(cfg: Self::ServiceConfig) -> ConfigChange {\n        ConfigChange::ClientChange(ClientServiceChange::Add(cfg))\n    }\n    fn get_services(&self) -> &HashMap<String, Self::ServiceConfig> {\n        &self.services\n    }\n}\n\npub struct ConfigWatcherHandle {\n    pub event_rx: mpsc::UnboundedReceiver<ConfigChange>,\n}\n\nimpl ConfigWatcherHandle {\n    pub async fn new(path: &Path, shutdown_rx: broadcast::Receiver<bool>) -> Result<Self> {\n        let (event_tx, event_rx) = mpsc::unbounded_channel();\n        let origin_cfg = Config::from_file(path).await?;\n\n        // Initial start\n        event_tx\n            .send(ConfigChange::General(Box::new(origin_cfg.clone())))\n            .unwrap();\n\n        tokio::spawn(config_watcher(\n            path.to_owned(),\n            shutdown_rx,\n            event_tx,\n            origin_cfg,\n        ));\n\n        Ok(ConfigWatcherHandle { event_rx })\n    }\n}\n\n// Fake config watcher when compiling without `notify`\n#[cfg(not(feature = \"notify\"))]\nasync fn config_watcher(\n    _path: PathBuf,\n    mut shutdown_rx: broadcast::Receiver<bool>,\n    _event_tx: mpsc::UnboundedSender<ConfigChange>,\n    _old: Config,\n) -> Result<()> {\n    // Do nothing except waiting for ctrl-c\n    let _ = shutdown_rx.recv().await;\n    Ok(())\n}\n\n#[cfg(feature = \"notify\")]\n#[instrument(skip(shutdown_rx, event_tx, old))]\nasync fn config_watcher(\n    path: PathBuf,\n    mut shutdown_rx: broadcast::Receiver<bool>,\n    event_tx: mpsc::UnboundedSender<ConfigChange>,\n    mut old: Config,\n) -> Result<()> {\n    let (fevent_tx, mut fevent_rx) = mpsc::unbounded_channel();\n    let path = if path.is_absolute() {\n        path\n    } else {\n        env::current_dir()?.join(path)\n    };\n    let parent_path = path.parent().expect(\"config file should have a parent dir\");\n    let path_clone = path.clone();\n    let mut watcher =\n        notify::recommended_watcher(move |res: Result<notify::Event, _>| match res {\n            Ok(e) => {\n                if matches!(e.kind, EventKind::Modify(_))\n                    && e.paths\n                        .iter()\n                        .map(|x| x.file_name())\n                        .any(|x| x == path_clone.file_name())\n                {\n                    let _ = fevent_tx.send(true);\n                }\n            }\n            Err(e) => error!(\"watch error: {:#}\", e),\n        })?;\n\n    watcher.watch(parent_path, RecursiveMode::NonRecursive)?;\n    info!(\"Start watching the config\");\n\n    loop {\n        tokio::select! {\n          e = fevent_rx.recv() => {\n            match e {\n              Some(_) => {\n                    info!(\"Rescan the configuration\");\n                    let new = match Config::from_file(&path).await.with_context(|| \"The changed configuration is invalid. Ignored\") {\n                      Ok(v) => v,\n                      Err(e) => {\n                        error!(\"{:#}\", e);\n                        // If the config is invalid, just ignore it\n                        continue;\n                      }\n                    };\n\n                    let events = calculate_events(&old, &new).into_iter().flatten();\n                    for event in events {\n                        event_tx.send(event)?;\n                    }\n\n                    old = new;\n              },\n              None => break\n            }\n          },\n          _ = shutdown_rx.recv() => break\n        }\n    }\n\n    info!(\"Config watcher exiting\");\n\n    Ok(())\n}\n\nfn calculate_events(old: &Config, new: &Config) -> Option<Vec<ConfigChange>> {\n    if old == new {\n        return None;\n    }\n\n    if (old.server.is_some() != new.server.is_some())\n        || (old.client.is_some() != new.client.is_some())\n    {\n        return Some(vec![ConfigChange::General(Box::new(new.clone()))]);\n    }\n\n    let mut ret = vec![];\n\n    if old.server != new.server {\n        match calculate_instance_config_events(\n            old.server.as_ref().unwrap(),\n            new.server.as_ref().unwrap(),\n        ) {\n            Some(mut v) => ret.append(&mut v),\n            None => return Some(vec![ConfigChange::General(Box::new(new.clone()))]),\n        }\n    }\n\n    if old.client != new.client {\n        match calculate_instance_config_events(\n            old.client.as_ref().unwrap(),\n            new.client.as_ref().unwrap(),\n        ) {\n            Some(mut v) => ret.append(&mut v),\n            None => return Some(vec![ConfigChange::General(Box::new(new.clone()))]),\n        }\n    }\n\n    Some(ret)\n}\n\n// None indicates a General change needed\nfn calculate_instance_config_events<T: InstanceConfig>(\n    old: &T,\n    new: &T,\n) -> Option<Vec<ConfigChange>> {\n    if !old.equal_without_service(new) {\n        return None;\n    }\n\n    let old = old.get_services();\n    let new = new.get_services();\n\n    let deletions = old\n        .keys()\n        .filter(|&name| new.get(name).is_none())\n        .map(|x| T::service_delete_change(x.to_owned()));\n\n    let addition = new\n        .iter()\n        .filter(|(name, c)| old.get(*name) != Some(*c))\n        .map(|(_, c)| T::service_add_change(c.clone()));\n\n    Some(deletions.chain(addition).collect())\n}\n\n#[cfg(test)]\nmod test {\n    use crate::config::ServerConfig;\n\n    use super::*;\n\n    // macro to create map or set literal\n    macro_rules! collection {\n        // map-like\n        ($($k:expr => $v:expr),* $(,)?) => {{\n            use std::iter::{Iterator, IntoIterator};\n            Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]))\n        }};\n    }\n\n    #[test]\n    fn test_calculate_events() {\n        struct Test {\n            old: Config,\n            new: Config,\n        }\n\n        let tests = [\n            Test {\n                old: Config {\n                    server: Some(Default::default()),\n                    client: None,\n                },\n                new: Config {\n                    server: Some(Default::default()),\n                    client: Some(Default::default()),\n                },\n            },\n            Test {\n                old: Config {\n                    server: Some(ServerConfig {\n                        bind_addr: String::from(\"127.0.0.1:2334\"),\n                        ..Default::default()\n                    }),\n                    client: None,\n                },\n                new: Config {\n                    server: Some(ServerConfig {\n                        bind_addr: String::from(\"127.0.0.1:2333\"),\n                        services: collection!(String::from(\"foo\") => Default::default()),\n                        ..Default::default()\n                    }),\n                    client: None,\n                },\n            },\n            Test {\n                old: Config {\n                    server: Some(Default::default()),\n                    client: None,\n                },\n                new: Config {\n                    server: Some(ServerConfig {\n                        services: collection!(String::from(\"foo\") => Default::default()),\n                        ..Default::default()\n                    }),\n                    client: None,\n                },\n            },\n            Test {\n                old: Config {\n                    server: Some(ServerConfig {\n                        services: collection!(String::from(\"foo\") => Default::default()),\n                        ..Default::default()\n                    }),\n                    client: None,\n                },\n                new: Config {\n                    server: Some(Default::default()),\n                    client: None,\n                },\n            },\n            Test {\n                old: Config {\n                    server: Some(ServerConfig {\n                        services: collection!(String::from(\"foo1\") => ServerServiceConfig::with_name(\"foo1\"), String::from(\"foo2\") => ServerServiceConfig::with_name(\"foo2\")),\n                        ..Default::default()\n                    }),\n                    client: Some(ClientConfig {\n                        services: collection!(String::from(\"foo1\") => ClientServiceConfig::with_name(\"foo1\"), String::from(\"foo2\") => ClientServiceConfig::with_name(\"foo2\")),\n                        ..Default::default()\n                    }),\n                },\n                new: Config {\n                    server: Some(ServerConfig {\n                        services: collection!(String::from(\"bar1\") => ServerServiceConfig::with_name(\"bar1\"), String::from(\"foo2\") => ServerServiceConfig::with_name(\"foo2\")),\n                        ..Default::default()\n                    }),\n                    client: Some(ClientConfig {\n                        services: collection!(String::from(\"bar1\") => ClientServiceConfig::with_name(\"bar1\"), String::from(\"bar2\") => ClientServiceConfig::with_name(\"bar2\")),\n                        ..Default::default()\n                    }),\n                },\n            },\n        ];\n\n        let mut expected = [\n            vec![ConfigChange::General(Box::new(tests[0].new.clone()))],\n            vec![ConfigChange::General(Box::new(tests[1].new.clone()))],\n            vec![ConfigChange::ServerChange(ServerServiceChange::Add(\n                Default::default(),\n            ))],\n            vec![ConfigChange::ServerChange(ServerServiceChange::Delete(\n                String::from(\"foo\"),\n            ))],\n            vec![\n                ConfigChange::ServerChange(ServerServiceChange::Delete(String::from(\"foo1\"))),\n                ConfigChange::ServerChange(ServerServiceChange::Add(\n                    tests[4].new.server.as_ref().unwrap().services[\"bar1\"].clone(),\n                )),\n                ConfigChange::ClientChange(ClientServiceChange::Delete(String::from(\"foo1\"))),\n                ConfigChange::ClientChange(ClientServiceChange::Delete(String::from(\"foo2\"))),\n                ConfigChange::ClientChange(ClientServiceChange::Add(\n                    tests[4].new.client.as_ref().unwrap().services[\"bar1\"].clone(),\n                )),\n                ConfigChange::ClientChange(ClientServiceChange::Add(\n                    tests[4].new.client.as_ref().unwrap().services[\"bar2\"].clone(),\n                )),\n            ],\n        ];\n\n        assert_eq!(tests.len(), expected.len());\n\n        for i in 0..tests.len() {\n            let mut actual = calculate_events(&tests[i].old, &tests[i].new).unwrap();\n\n            let get_key = |x: &ConfigChange| -> String {\n                match x {\n                    ConfigChange::General(_) => String::from(\"g\"),\n                    ConfigChange::ServerChange(sc) => match sc {\n                        ServerServiceChange::Add(c) => \"s_add_\".to_owned() + &c.name,\n                        ServerServiceChange::Delete(s) => \"s_del_\".to_owned() + s,\n                    },\n                    ConfigChange::ClientChange(sc) => match sc {\n                        ClientServiceChange::Add(c) => \"c_add_\".to_owned() + &c.name,\n                        ClientServiceChange::Delete(s) => \"c_del_\".to_owned() + s,\n                    },\n                }\n            };\n\n            actual.sort_by_cached_key(get_key);\n            expected[i].sort_by_cached_key(get_key);\n\n            assert_eq!(actual, expected[i]);\n        }\n\n        // No changes\n        assert_eq!(\n            calculate_events(\n                &Config {\n                    server: Default::default(),\n                    client: None,\n                },\n                &Config {\n                    server: Default::default(),\n                    client: None,\n                },\n            ),\n            None\n        );\n    }\n}\n"
  },
  {
    "path": "src/constants.rs",
    "content": "use backoff::ExponentialBackoff;\nuse std::time::Duration;\n\n// FIXME: Determine reasonable size\n/// UDP MTU. Currently far larger than necessary\npub const UDP_BUFFER_SIZE: usize = 2048;\npub const UDP_SENDQ_SIZE: usize = 1024;\npub const UDP_TIMEOUT: u64 = 60;\n\npub fn listen_backoff() -> ExponentialBackoff {\n    ExponentialBackoff {\n        max_elapsed_time: None,\n        max_interval: Duration::from_secs(1),\n        ..Default::default()\n    }\n}\n\npub fn run_control_chan_backoff(interval: u64) -> ExponentialBackoff {\n    ExponentialBackoff {\n        randomization_factor: 0.2,\n        max_elapsed_time: None,\n        multiplier: 3.0,\n        max_interval: Duration::from_secs(interval),\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/helper.rs",
    "content": "use anyhow::{anyhow, Context, Result};\nuse async_http_proxy::{http_connect_tokio, http_connect_tokio_with_basic_auth};\nuse backoff::{backoff::Backoff, Notify};\nuse socket2::{SockRef, TcpKeepalive};\nuse std::{future::Future, net::SocketAddr, time::Duration};\nuse tokio::io::{AsyncWrite, AsyncWriteExt};\nuse tokio::{\n    net::{lookup_host, TcpStream, ToSocketAddrs, UdpSocket},\n    sync::broadcast,\n};\nuse tracing::trace;\nuse url::Url;\n\nuse crate::transport::AddrMaybeCached;\n\n// Tokio hesitates to expose this option...So we have to do it on our own :(\n// The good news is that using socket2 it can be easily done, without losing portability.\n// See https://github.com/tokio-rs/tokio/issues/3082\npub fn try_set_tcp_keepalive(\n    conn: &TcpStream,\n    keepalive_duration: Duration,\n    keepalive_interval: Duration,\n) -> Result<()> {\n    let s = SockRef::from(conn);\n    let keepalive = TcpKeepalive::new()\n        .with_time(keepalive_duration)\n        .with_interval(keepalive_interval);\n\n    trace!(\n        \"Set TCP keepalive {:?} {:?}\",\n        keepalive_duration,\n        keepalive_interval\n    );\n\n    Ok(s.set_tcp_keepalive(&keepalive)?)\n}\n\n#[allow(dead_code)]\npub fn feature_not_compile(feature: &str) -> ! {\n    panic!(\n        \"The feature '{}' is not compiled in this binary. Please re-compile rathole\",\n        feature\n    )\n}\n\n#[allow(dead_code)]\npub fn feature_neither_compile(feature1: &str, feature2: &str) -> ! {\n    panic!(\n        \"Neither of the feature '{}' or '{}' is compiled in this binary. Please re-compile rathole\",\n        feature1, feature2\n    )\n}\n\npub async fn to_socket_addr<A: ToSocketAddrs>(addr: A) -> Result<SocketAddr> {\n    lookup_host(addr)\n        .await?\n        .next()\n        .ok_or_else(|| anyhow!(\"Failed to lookup the host\"))\n}\n\npub fn host_port_pair(s: &str) -> Result<(&str, u16)> {\n    let semi = s.rfind(':').expect(\"missing semicolon\");\n    Ok((&s[..semi], s[semi + 1..].parse()?))\n}\n\n/// Create a UDP socket and connect to `addr`\npub async fn udp_connect<A: ToSocketAddrs>(addr: A, prefer_ipv6: bool) -> Result<UdpSocket> {\n\n    let (socket_addr, bind_addr);\n\n    match prefer_ipv6 {\n        false => {\n            socket_addr = to_socket_addr(addr).await?;\n\n            bind_addr = match socket_addr {\n                SocketAddr::V4(_) => \"0.0.0.0:0\",\n                SocketAddr::V6(_) => \":::0\",\n            };\n        },\n        true => {\n            let all_host_addresses: Vec<SocketAddr> = lookup_host(addr).await?.collect();\n\n            // Try to find an IPv6 address\n            match all_host_addresses.clone().iter().find(|x| x.is_ipv6()) {\n                Some(socket_addr_ipv6) => {\n                    socket_addr = *socket_addr_ipv6;\n                    bind_addr = \":::0\";\n                },\n                None => {\n                    let socket_addr_ipv4 = all_host_addresses.iter().find(|x| x.is_ipv4());\n                    match socket_addr_ipv4 {\n                        None => return Err(anyhow!(\"Failed to lookup the host\")),\n                        // fallback to IPv4\n                        Some(socket_addr_ipv4) => {\n                            socket_addr = *socket_addr_ipv4;\n                            bind_addr = \"0.0.0.0:0\";\n                        }\n                    }\n                }\n            }\n        }\n    };\n    let s = UdpSocket::bind(bind_addr).await?;\n    s.connect(socket_addr).await?;\n    s.connect(socket_addr).await?;\n    Ok(s)\n}\n\n/// Create a TcpStream using a proxy\n/// e.g. socks5://user:pass@127.0.0.1:1080 http://127.0.0.1:8080\npub async fn tcp_connect_with_proxy(\n    addr: &AddrMaybeCached,\n    proxy: Option<&Url>,\n) -> Result<TcpStream> {\n    if let Some(url) = proxy {\n        let addr = &addr.addr;\n        let mut s = TcpStream::connect((\n            url.host_str().expect(\"proxy url should have host field\"),\n            url.port().expect(\"proxy url should have port field\"),\n        ))\n        .await?;\n\n        let auth = if !url.username().is_empty() || url.password().is_some() {\n            Some(async_socks5::Auth {\n                username: url.username().into(),\n                password: url.password().unwrap_or(\"\").into(),\n            })\n        } else {\n            None\n        };\n        match url.scheme() {\n            \"socks5\" => {\n                async_socks5::connect(&mut s, host_port_pair(addr)?, auth).await?;\n            }\n            \"http\" => {\n                let (host, port) = host_port_pair(addr)?;\n                match auth {\n                    Some(auth) => {\n                        http_connect_tokio_with_basic_auth(\n                            &mut s,\n                            host,\n                            port,\n                            &auth.username,\n                            &auth.password,\n                        )\n                        .await?\n                    }\n                    None => http_connect_tokio(&mut s, host, port).await?,\n                }\n            }\n            _ => panic!(\"unknown proxy scheme\"),\n        }\n        Ok(s)\n    } else {\n        Ok(match addr.socket_addr {\n            Some(s) => TcpStream::connect(s).await?,\n            None => TcpStream::connect(&addr.addr).await?,\n        })\n    }\n}\n\n// Wrapper of retry_notify\npub async fn retry_notify_with_deadline<I, E, Fn, Fut, B, N>(\n    backoff: B,\n    operation: Fn,\n    notify: N,\n    deadline: &mut broadcast::Receiver<bool>,\n) -> Result<I>\nwhere\n    E: std::error::Error + Send + Sync + 'static,\n    B: Backoff,\n    Fn: FnMut() -> Fut,\n    Fut: Future<Output = std::result::Result<I, backoff::Error<E>>>,\n    N: Notify<E>,\n{\n    tokio::select! {\n        v = backoff::future::retry_notify(backoff, operation, notify) => {\n            v.map_err(anyhow::Error::new)\n        }\n        _ = deadline.recv() => {\n            Err(anyhow!(\"shutdown\"))\n        }\n    }\n}\n\npub async fn write_and_flush<T>(conn: &mut T, data: &[u8]) -> Result<()>\nwhere\n    T: AsyncWrite + Unpin,\n{\n    conn.write_all(data)\n        .await\n        .with_context(|| \"Failed to write data\")?;\n    conn.flush().await.with_context(|| \"Failed to flush data\")?;\n    Ok(())\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "mod cli;\nmod config;\nmod config_watcher;\nmod constants;\nmod helper;\nmod multi_map;\nmod protocol;\nmod transport;\n\npub use cli::Cli;\nuse cli::KeypairType;\npub use config::Config;\npub use constants::UDP_BUFFER_SIZE;\n\nuse anyhow::Result;\nuse tokio::sync::{broadcast, mpsc};\nuse tracing::{debug, info};\n\n#[cfg(feature = \"client\")]\nmod client;\n#[cfg(feature = \"client\")]\nuse client::run_client;\n\n#[cfg(feature = \"server\")]\nmod server;\n#[cfg(feature = \"server\")]\nuse server::run_server;\n\nuse crate::config_watcher::{ConfigChange, ConfigWatcherHandle};\n\nconst DEFAULT_CURVE: KeypairType = KeypairType::X25519;\n\nfn get_str_from_keypair_type(curve: KeypairType) -> &'static str {\n    match curve {\n        KeypairType::X25519 => \"25519\",\n        KeypairType::X448 => \"448\",\n    }\n}\n\n#[cfg(feature = \"noise\")]\nfn genkey(curve: Option<KeypairType>) -> Result<()> {\n    let curve = curve.unwrap_or(DEFAULT_CURVE);\n    let builder = snowstorm::Builder::new(\n        format!(\n            \"Noise_KK_{}_ChaChaPoly_BLAKE2s\",\n            get_str_from_keypair_type(curve)\n        )\n        .parse()?,\n    );\n    let keypair = builder.generate_keypair()?;\n\n    println!(\"Private Key:\\n{}\\n\", base64::encode(keypair.private));\n    println!(\"Public Key:\\n{}\", base64::encode(keypair.public));\n    Ok(())\n}\n\n#[cfg(not(feature = \"noise\"))]\nfn genkey(curve: Option<KeypairType>) -> Result<()> {\n    crate::helper::feature_not_compile(\"nosie\")\n}\n\npub async fn run(args: Cli, shutdown_rx: broadcast::Receiver<bool>) -> Result<()> {\n    if args.genkey.is_some() {\n        return genkey(args.genkey.unwrap());\n    }\n\n    // Raise `nofile` limit on linux and mac\n    fdlimit::raise_fd_limit();\n\n    // Spawn a config watcher. The watcher will send a initial signal to start the instance with a config\n    let config_path = args.config_path.as_ref().unwrap();\n    let mut cfg_watcher = ConfigWatcherHandle::new(config_path, shutdown_rx).await?;\n\n    // shutdown_tx owns the instance\n    let (shutdown_tx, _) = broadcast::channel(1);\n\n    // (The join handle of the last instance, The service update channel sender)\n    let mut last_instance: Option<(tokio::task::JoinHandle<_>, mpsc::Sender<ConfigChange>)> = None;\n\n    while let Some(e) = cfg_watcher.event_rx.recv().await {\n        match e {\n            ConfigChange::General(config) => {\n                if let Some((i, _)) = last_instance {\n                    info!(\"General configuration change detected. Restarting...\");\n                    shutdown_tx.send(true)?;\n                    i.await??;\n                }\n\n                debug!(\"{:?}\", config);\n\n                let (service_update_tx, service_update_rx) = mpsc::channel(1024);\n\n                last_instance = Some((\n                    tokio::spawn(run_instance(\n                        *config,\n                        args.clone(),\n                        shutdown_tx.subscribe(),\n                        service_update_rx,\n                    )),\n                    service_update_tx,\n                ));\n            }\n            ev => {\n                info!(\"Service change detected. {:?}\", ev);\n                if let Some((_, service_update_tx)) = &last_instance {\n                    let _ = service_update_tx.send(ev).await;\n                }\n            }\n        }\n    }\n\n    let _ = shutdown_tx.send(true);\n\n    Ok(())\n}\n\nasync fn run_instance(\n    config: Config,\n    args: Cli,\n    shutdown_rx: broadcast::Receiver<bool>,\n    service_update: mpsc::Receiver<ConfigChange>,\n) -> Result<()> {\n    match determine_run_mode(&config, &args) {\n        RunMode::Undetermine => panic!(\"Cannot determine running as a server or a client\"),\n        RunMode::Client => {\n            #[cfg(not(feature = \"client\"))]\n            crate::helper::feature_not_compile(\"client\");\n            #[cfg(feature = \"client\")]\n            run_client(config, shutdown_rx, service_update).await\n        }\n        RunMode::Server => {\n            #[cfg(not(feature = \"server\"))]\n            crate::helper::feature_not_compile(\"server\");\n            #[cfg(feature = \"server\")]\n            run_server(config, shutdown_rx, service_update).await\n        }\n    }\n}\n\n#[derive(PartialEq, Eq, Debug)]\nenum RunMode {\n    Server,\n    Client,\n    Undetermine,\n}\n\nfn determine_run_mode(config: &Config, args: &Cli) -> RunMode {\n    use RunMode::*;\n    if args.client && args.server {\n        Undetermine\n    } else if args.client {\n        Client\n    } else if args.server {\n        Server\n    } else if config.client.is_some() && config.server.is_none() {\n        Client\n    } else if config.server.is_some() && config.client.is_none() {\n        Server\n    } else {\n        Undetermine\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_determine_run_mode() {\n        use config::*;\n        use RunMode::*;\n\n        struct T {\n            cfg_s: bool,\n            cfg_c: bool,\n            arg_s: bool,\n            arg_c: bool,\n            run_mode: RunMode,\n        }\n\n        let tests = [\n            T {\n                cfg_s: false,\n                cfg_c: false,\n                arg_s: false,\n                arg_c: false,\n                run_mode: Undetermine,\n            },\n            T {\n                cfg_s: true,\n                cfg_c: false,\n                arg_s: false,\n                arg_c: false,\n                run_mode: Server,\n            },\n            T {\n                cfg_s: false,\n                cfg_c: true,\n                arg_s: false,\n                arg_c: false,\n                run_mode: Client,\n            },\n            T {\n                cfg_s: true,\n                cfg_c: true,\n                arg_s: false,\n                arg_c: false,\n                run_mode: Undetermine,\n            },\n            T {\n                cfg_s: true,\n                cfg_c: true,\n                arg_s: true,\n                arg_c: false,\n                run_mode: Server,\n            },\n            T {\n                cfg_s: true,\n                cfg_c: true,\n                arg_s: false,\n                arg_c: true,\n                run_mode: Client,\n            },\n            T {\n                cfg_s: true,\n                cfg_c: true,\n                arg_s: true,\n                arg_c: true,\n                run_mode: Undetermine,\n            },\n        ];\n\n        for t in tests {\n            let config = Config {\n                server: match t.cfg_s {\n                    true => Some(ServerConfig::default()),\n                    false => None,\n                },\n                client: match t.cfg_c {\n                    true => Some(ClientConfig::default()),\n                    false => None,\n                },\n            };\n\n            let args = Cli {\n                config_path: Some(std::path::PathBuf::new()),\n                server: t.arg_s,\n                client: t.arg_c,\n                ..Default::default()\n            };\n\n            assert_eq!(determine_run_mode(&config, &args), t.run_mode);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "use anyhow::Result;\nuse clap::Parser;\nuse rathole::{run, Cli};\nuse tokio::{signal, sync::broadcast};\nuse tracing_subscriber::EnvFilter;\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let args = Cli::parse();\n\n    let (shutdown_tx, shutdown_rx) = broadcast::channel::<bool>(1);\n    tokio::spawn(async move {\n        if let Err(e) = signal::ctrl_c().await {\n            // Something really weird happened. So just panic\n            panic!(\"Failed to listen for the ctrl-c signal: {:?}\", e);\n        }\n\n        if let Err(e) = shutdown_tx.send(true) {\n            // shutdown signal must be catched and handle properly\n            // `rx` must not be dropped\n            panic!(\"Failed to send shutdown signal: {:?}\", e);\n        }\n    });\n\n    #[cfg(feature = \"console\")]\n    {\n        console_subscriber::init();\n\n        tracing::info!(\"console_subscriber enabled\");\n    }\n    #[cfg(not(feature = \"console\"))]\n    {\n        let is_atty = atty::is(atty::Stream::Stdout);\n\n        let level = \"info\"; // if RUST_LOG not present, use `info` level\n        tracing_subscriber::fmt()\n            .with_env_filter(\n                EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::from(level)),\n            )\n            .with_ansi(is_atty)\n            .init();\n    }\n\n    run(args, shutdown_rx).await\n}\n"
  },
  {
    "path": "src/multi_map.rs",
    "content": "use std::borrow::Borrow;\nuse std::collections::HashMap;\nuse std::hash::{Hash, Hasher};\n\nstruct RawItem<K1, K2, V>(*mut (K1, K2, V));\nunsafe impl<K1, K2, V> Send for RawItem<K1, K2, V> {}\nunsafe impl<K1, K2, V> Sync for RawItem<K1, K2, V> {}\n\n/// MultiMap is a hash map that can index an item by two keys\n/// For example, after an item with key (a, b) is insert, `map.get1(a)` and\n/// `map.get2(b)` both returns the item. Likewise the `remove1` and `remove2`.\npub struct MultiMap<K1, K2, V> {\n    map1: HashMap<Key<K1>, RawItem<K1, K2, V>>,\n    map2: HashMap<Key<K2>, RawItem<K1, K2, V>>,\n}\n\nstruct Key<T>(*const T);\n\nunsafe impl<T> Send for Key<T> {}\nunsafe impl<T> Sync for Key<T> {}\n\nimpl<T> Borrow<T> for Key<T> {\n    fn borrow(&self) -> &T {\n        unsafe { &*self.0 }\n    }\n}\n\nimpl<T: Hash> Hash for Key<T> {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        (self.borrow() as &T).hash(state)\n    }\n}\n\nimpl<T: PartialEq> PartialEq for Key<T> {\n    fn eq(&self, other: &Self) -> bool {\n        (self.borrow() as &T).eq(other.borrow())\n    }\n}\n\nimpl<T: Eq> Eq for Key<T> {}\n\nimpl<K1, K2, V> MultiMap<K1, K2, V> {\n    pub fn new() -> Self {\n        MultiMap {\n            map1: HashMap::new(),\n            map2: HashMap::new(),\n        }\n    }\n}\n\n#[allow(dead_code)]\nimpl<K1, K2, V> MultiMap<K1, K2, V>\nwhere\n    K1: Hash + Eq + Send,\n    K2: Hash + Eq + Send,\n    V: Send,\n{\n    pub fn insert(&mut self, k1: K1, k2: K2, v: V) -> Result<(), (K1, K2, V)> {\n        if self.map1.contains_key(&k1) || self.map2.contains_key(&k2) {\n            return Err((k1, k2, v));\n        }\n        let item = Box::new((k1, k2, v));\n        let k1 = Key(&item.0);\n        let k2 = Key(&item.1);\n        let item = Box::into_raw(item);\n        self.map1.insert(k1, RawItem(item));\n        self.map2.insert(k2, RawItem(item));\n        Ok(())\n    }\n\n    pub fn get1(&self, k1: &K1) -> Option<&V> {\n        let item = self.map1.get(k1)?;\n        let item = unsafe { &*item.0 };\n        Some(&item.2)\n    }\n\n    pub fn get1_mut(&mut self, k1: &K1) -> Option<&mut V> {\n        let item = self.map1.get(k1)?;\n        let item = unsafe { &mut *item.0 };\n        Some(&mut item.2)\n    }\n\n    pub fn get2(&self, k2: &K2) -> Option<&V> {\n        let item = self.map2.get(k2)?;\n        let item = unsafe { &*item.0 };\n        Some(&item.2)\n    }\n\n    pub fn get_mut2(&mut self, k2: &K2) -> Option<&mut V> {\n        let item = self.map2.get(k2)?;\n        let item = unsafe { &mut *item.0 };\n        Some(&mut item.2)\n    }\n\n    pub fn remove1(&mut self, k1: &K1) -> Option<V> {\n        let item = self.map1.remove(k1)?;\n        let item = unsafe { Box::from_raw(item.0) };\n        self.map2.remove(&item.1);\n        Some(item.2)\n    }\n\n    pub fn remove2(&mut self, k2: &K2) -> Option<V> {\n        let item = self.map2.remove(k2)?;\n        let item = unsafe { Box::from_raw(item.0) };\n        self.map1.remove(&item.0);\n        Some(item.2)\n    }\n}\n\nimpl<K1, K2, V> Drop for MultiMap<K1, K2, V> {\n    fn drop(&mut self) {\n        self.map1.clear();\n        self.map2\n            .drain()\n            .for_each(|(_, item)| drop(unsafe { Box::from_raw(item.0) }));\n    }\n}\n"
  },
  {
    "path": "src/protocol.rs",
    "content": "pub const HASH_WIDTH_IN_BYTES: usize = 32;\n\nuse anyhow::{bail, Context, Result};\nuse bytes::{Bytes, BytesMut};\nuse lazy_static::lazy_static;\nuse serde::{Deserialize, Serialize};\nuse std::net::SocketAddr;\nuse tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};\nuse tracing::trace;\n\ntype ProtocolVersion = u8;\nconst _PROTO_V0: u8 = 0u8;\nconst PROTO_V1: u8 = 1u8;\n\npub const CURRENT_PROTO_VERSION: ProtocolVersion = PROTO_V1;\n\npub type Digest = [u8; HASH_WIDTH_IN_BYTES];\n\n#[derive(Deserialize, Serialize, Debug)]\npub enum Hello {\n    ControlChannelHello(ProtocolVersion, Digest), // sha256sum(service name) or a nonce\n    DataChannelHello(ProtocolVersion, Digest),    // token provided by CreateDataChannel\n}\n\n#[derive(Deserialize, Serialize, Debug)]\npub struct Auth(pub Digest);\n\n#[derive(Deserialize, Serialize, Debug)]\npub enum Ack {\n    Ok,\n    ServiceNotExist,\n    AuthFailed,\n}\n\nimpl std::fmt::Display for Ack {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                Ack::Ok => \"Ok\",\n                Ack::ServiceNotExist => \"Service not exist\",\n                Ack::AuthFailed => \"Incorrect token\",\n            }\n        )\n    }\n}\n\n#[derive(Deserialize, Serialize, Debug)]\npub enum ControlChannelCmd {\n    CreateDataChannel,\n    HeartBeat,\n}\n\n#[derive(Deserialize, Serialize, Debug)]\npub enum DataChannelCmd {\n    StartForwardTcp,\n    StartForwardUdp,\n}\n\ntype UdpPacketLen = u16; // `u16` should be enough for any practical UDP traffic on the Internet\n#[derive(Deserialize, Serialize, Debug)]\nstruct UdpHeader {\n    from: SocketAddr,\n    len: UdpPacketLen,\n}\n\n#[derive(Debug)]\npub struct UdpTraffic {\n    pub from: SocketAddr,\n    pub data: Bytes,\n}\n\nimpl UdpTraffic {\n    pub async fn write<T: AsyncWrite + Unpin>(&self, writer: &mut T) -> Result<()> {\n        let hdr = UdpHeader {\n            from: self.from,\n            len: self.data.len() as UdpPacketLen,\n        };\n\n        let v = bincode::serialize(&hdr).unwrap();\n\n        trace!(\"Write {:?} of length {}\", hdr, v.len());\n        writer.write_u8(v.len() as u8).await?;\n        writer.write_all(&v).await?;\n\n        writer.write_all(&self.data).await?;\n\n        Ok(())\n    }\n\n    #[allow(dead_code)]\n    pub async fn write_slice<T: AsyncWrite + Unpin>(\n        writer: &mut T,\n        from: SocketAddr,\n        data: &[u8],\n    ) -> Result<()> {\n        let hdr = UdpHeader {\n            from,\n            len: data.len() as UdpPacketLen,\n        };\n\n        let v = bincode::serialize(&hdr).unwrap();\n\n        trace!(\"Write {:?} of length {}\", hdr, v.len());\n        writer.write_u8(v.len() as u8).await?;\n        writer.write_all(&v).await?;\n\n        writer.write_all(data).await?;\n\n        Ok(())\n    }\n\n    pub async fn read<T: AsyncRead + Unpin>(reader: &mut T, hdr_len: u8) -> Result<UdpTraffic> {\n        let mut buf = vec![0; hdr_len as usize];\n        reader\n            .read_exact(&mut buf)\n            .await\n            .with_context(|| \"Failed to read udp header\")?;\n\n        let hdr: UdpHeader =\n            bincode::deserialize(&buf).with_context(|| \"Failed to deserialize UdpHeader\")?;\n\n        trace!(\"hdr {:?}\", hdr);\n\n        let mut data = BytesMut::new();\n        data.resize(hdr.len as usize, 0);\n        reader.read_exact(&mut data).await?;\n\n        Ok(UdpTraffic {\n            from: hdr.from,\n            data: data.freeze(),\n        })\n    }\n}\n\npub fn digest(data: &[u8]) -> Digest {\n    use sha2::{Digest, Sha256};\n    let d = Sha256::new().chain_update(data).finalize();\n    d.into()\n}\n\nstruct PacketLength {\n    hello: usize,\n    ack: usize,\n    auth: usize,\n    c_cmd: usize,\n    d_cmd: usize,\n}\n\nimpl PacketLength {\n    pub fn new() -> PacketLength {\n        let username = \"default\";\n        let d = digest(username.as_bytes());\n        let hello = bincode::serialized_size(&Hello::ControlChannelHello(CURRENT_PROTO_VERSION, d))\n            .unwrap() as usize;\n        let c_cmd =\n            bincode::serialized_size(&ControlChannelCmd::CreateDataChannel).unwrap() as usize;\n        let d_cmd = bincode::serialized_size(&DataChannelCmd::StartForwardTcp).unwrap() as usize;\n        let ack = Ack::Ok;\n        let ack = bincode::serialized_size(&ack).unwrap() as usize;\n\n        let auth = bincode::serialized_size(&Auth(d)).unwrap() as usize;\n        PacketLength {\n            hello,\n            ack,\n            auth,\n            c_cmd,\n            d_cmd,\n        }\n    }\n}\n\nlazy_static! {\n    static ref PACKET_LEN: PacketLength = PacketLength::new();\n}\n\npub async fn read_hello<T: AsyncRead + AsyncWrite + Unpin>(conn: &mut T) -> Result<Hello> {\n    let mut buf = vec![0u8; PACKET_LEN.hello];\n    conn.read_exact(&mut buf)\n        .await\n        .with_context(|| \"Failed to read hello\")?;\n    let hello = bincode::deserialize(&buf).with_context(|| \"Failed to deserialize hello\")?;\n\n    match hello {\n        Hello::ControlChannelHello(v, _) => {\n            if v != CURRENT_PROTO_VERSION {\n                bail!(\n                    \"Protocol version mismatched. Expected {}, got {}. Please update `rathole`.\",\n                    CURRENT_PROTO_VERSION,\n                    v\n                );\n            }\n        }\n        Hello::DataChannelHello(v, _) => {\n            if v != CURRENT_PROTO_VERSION {\n                bail!(\n                    \"Protocol version mismatched. Expected {}, got {}. Please update `rathole`.\",\n                    CURRENT_PROTO_VERSION,\n                    v\n                );\n            }\n        }\n    }\n\n    Ok(hello)\n}\n\npub async fn read_auth<T: AsyncRead + AsyncWrite + Unpin>(conn: &mut T) -> Result<Auth> {\n    let mut buf = vec![0u8; PACKET_LEN.auth];\n    conn.read_exact(&mut buf)\n        .await\n        .with_context(|| \"Failed to read auth\")?;\n    bincode::deserialize(&buf).with_context(|| \"Failed to deserialize auth\")\n}\n\npub async fn read_ack<T: AsyncRead + AsyncWrite + Unpin>(conn: &mut T) -> Result<Ack> {\n    let mut bytes = vec![0u8; PACKET_LEN.ack];\n    conn.read_exact(&mut bytes)\n        .await\n        .with_context(|| \"Failed to read ack\")?;\n    bincode::deserialize(&bytes).with_context(|| \"Failed to deserialize ack\")\n}\n\npub async fn read_control_cmd<T: AsyncRead + AsyncWrite + Unpin>(\n    conn: &mut T,\n) -> Result<ControlChannelCmd> {\n    let mut bytes = vec![0u8; PACKET_LEN.c_cmd];\n    conn.read_exact(&mut bytes)\n        .await\n        .with_context(|| \"Failed to read cmd\")?;\n    bincode::deserialize(&bytes).with_context(|| \"Failed to deserialize control cmd\")\n}\n\npub async fn read_data_cmd<T: AsyncRead + AsyncWrite + Unpin>(\n    conn: &mut T,\n) -> Result<DataChannelCmd> {\n    let mut bytes = vec![0u8; PACKET_LEN.d_cmd];\n    conn.read_exact(&mut bytes)\n        .await\n        .with_context(|| \"Failed to read cmd\")?;\n    bincode::deserialize(&bytes).with_context(|| \"Failed to deserialize data cmd\")\n}\n"
  },
  {
    "path": "src/server.rs",
    "content": "use crate::config::{Config, ServerConfig, ServerServiceConfig, ServiceType, TransportType};\nuse crate::config_watcher::{ConfigChange, ServerServiceChange};\nuse crate::constants::{listen_backoff, UDP_BUFFER_SIZE};\nuse crate::helper::{retry_notify_with_deadline, write_and_flush};\nuse crate::multi_map::MultiMap;\nuse crate::protocol::Hello::{ControlChannelHello, DataChannelHello};\nuse crate::protocol::{\n    self, read_auth, read_hello, Ack, ControlChannelCmd, DataChannelCmd, Hello, UdpTraffic,\n    HASH_WIDTH_IN_BYTES,\n};\nuse crate::transport::{SocketOpts, TcpTransport, Transport};\nuse anyhow::{anyhow, bail, Context, Result};\nuse backoff::backoff::Backoff;\nuse backoff::ExponentialBackoff;\n\nuse rand::RngCore;\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse std::time::Duration;\nuse tokio::io::{self, copy_bidirectional, AsyncReadExt, AsyncWriteExt};\nuse tokio::net::{TcpListener, TcpStream, UdpSocket};\nuse tokio::sync::{broadcast, mpsc, RwLock};\nuse tokio::time;\nuse tracing::{debug, error, info, info_span, instrument, warn, Instrument, Span};\n\n#[cfg(feature = \"noise\")]\nuse crate::transport::NoiseTransport;\n#[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\nuse crate::transport::TlsTransport;\n#[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\nuse crate::transport::WebsocketTransport;\n\ntype ServiceDigest = protocol::Digest; // SHA256 of a service name\ntype Nonce = protocol::Digest; // Also called `session_key`\n\nconst TCP_POOL_SIZE: usize = 8; // The number of cached connections for TCP servies\nconst UDP_POOL_SIZE: usize = 2; // The number of cached connections for UDP services\nconst CHAN_SIZE: usize = 2048; // The capacity of various chans\nconst HANDSHAKE_TIMEOUT: u64 = 5; // Timeout for transport handshake\n\n// The entrypoint of running a server\npub async fn run_server(\n    config: Config,\n    shutdown_rx: broadcast::Receiver<bool>,\n    update_rx: mpsc::Receiver<ConfigChange>,\n) -> Result<()> {\n    let config = match config.server {\n            Some(config) => config,\n            None => {\n                return Err(anyhow!(\"Try to run as a server, but the configuration is missing. Please add the `[server]` block\"))\n            }\n        };\n\n    match config.transport.transport_type {\n        TransportType::Tcp => {\n            let mut server = Server::<TcpTransport>::from(config).await?;\n            server.run(shutdown_rx, update_rx).await?;\n        }\n        TransportType::Tls => {\n            #[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\n            {\n                let mut server = Server::<TlsTransport>::from(config).await?;\n                server.run(shutdown_rx, update_rx).await?;\n            }\n            #[cfg(not(any(feature = \"native-tls\", feature = \"rustls\")))]\n            crate::helper::feature_neither_compile(\"native-tls\", \"rustls\")\n        }\n        TransportType::Noise => {\n            #[cfg(feature = \"noise\")]\n            {\n                let mut server = Server::<NoiseTransport>::from(config).await?;\n                server.run(shutdown_rx, update_rx).await?;\n            }\n            #[cfg(not(feature = \"noise\"))]\n            crate::helper::feature_not_compile(\"noise\")\n        }\n        TransportType::Websocket => {\n            #[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\n            {\n                let mut server = Server::<WebsocketTransport>::from(config).await?;\n                server.run(shutdown_rx, update_rx).await?;\n            }\n            #[cfg(not(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\")))]\n            crate::helper::feature_neither_compile(\"websocket-native-tls\", \"websocket-rustls\")\n        }\n    }\n\n    Ok(())\n}\n\n// A hash map of ControlChannelHandles, indexed by ServiceDigest or Nonce\n// See also MultiMap\ntype ControlChannelMap<T> = MultiMap<ServiceDigest, Nonce, ControlChannelHandle<T>>;\n\n// Server holds all states of running a server\nstruct Server<T: Transport> {\n    // `[server]` config\n    config: Arc<ServerConfig>,\n\n    // `[server.services]` config, indexed by ServiceDigest\n    services: Arc<RwLock<HashMap<ServiceDigest, ServerServiceConfig>>>,\n    // Collection of contorl channels\n    control_channels: Arc<RwLock<ControlChannelMap<T>>>,\n    // Wrapper around the transport layer\n    transport: Arc<T>,\n}\n\n// Generate a hash map of services which is indexed by ServiceDigest\nfn generate_service_hashmap(\n    server_config: &ServerConfig,\n) -> HashMap<ServiceDigest, ServerServiceConfig> {\n    let mut ret = HashMap::new();\n    for u in &server_config.services {\n        ret.insert(protocol::digest(u.0.as_bytes()), (*u.1).clone());\n    }\n    ret\n}\n\nimpl<T: 'static + Transport> Server<T> {\n    // Create a server from `[server]`\n    pub async fn from(config: ServerConfig) -> Result<Server<T>> {\n        let config = Arc::new(config);\n        let services = Arc::new(RwLock::new(generate_service_hashmap(&config)));\n        let control_channels = Arc::new(RwLock::new(ControlChannelMap::new()));\n        let transport = Arc::new(T::new(&config.transport)?);\n        Ok(Server {\n            config,\n            services,\n            control_channels,\n            transport,\n        })\n    }\n\n    // The entry point of Server\n    pub async fn run(\n        &mut self,\n        mut shutdown_rx: broadcast::Receiver<bool>,\n        mut update_rx: mpsc::Receiver<ConfigChange>,\n    ) -> Result<()> {\n        // Listen at `server.bind_addr`\n        let l = self\n            .transport\n            .bind(&self.config.bind_addr)\n            .await\n            .with_context(|| \"Failed to listen at `server.bind_addr`\")?;\n        info!(\"Listening at {}\", self.config.bind_addr);\n\n        // Retry at least every 100ms\n        let mut backoff = ExponentialBackoff {\n            max_interval: Duration::from_millis(100),\n            max_elapsed_time: None,\n            ..Default::default()\n        };\n\n        // Wait for connections and shutdown signals\n        loop {\n            tokio::select! {\n                // Wait for incoming control and data channels\n                ret = self.transport.accept(&l) => {\n                    match ret {\n                        Err(err) => {\n                            // Detects whether it's an IO error\n                            if let Some(err) = err.downcast_ref::<io::Error>() {\n                                // If it is an IO error, then it's possibly an\n                                // EMFILE. So sleep for a while and retry\n                                // TODO: Only sleep for EMFILE, ENFILE, ENOMEM, ENOBUFS\n                                if let Some(d) = backoff.next_backoff() {\n                                    error!(\"Failed to accept: {:#}. Retry in {:?}...\", err, d);\n                                    time::sleep(d).await;\n                                } else {\n                                    // This branch will never be executed according to the current retry policy\n                                    error!(\"Too many retries. Aborting...\");\n                                    break;\n                                }\n                            }\n                            // If it's not an IO error, then it comes from\n                            // the transport layer, so just ignore it\n                        }\n                        Ok((conn, addr)) => {\n                            backoff.reset();\n\n                            // Do transport handshake with a timeout\n                            match time::timeout(Duration::from_secs(HANDSHAKE_TIMEOUT), self.transport.handshake(conn)).await {\n                                Ok(conn) => {\n                                    match conn.with_context(|| \"Failed to do transport handshake\") {\n                                        Ok(conn) => {\n                                            let services = self.services.clone();\n                                            let control_channels = self.control_channels.clone();\n                                            let server_config = self.config.clone();\n                                            tokio::spawn(async move {\n                                                if let Err(err) = handle_connection(conn, services, control_channels, server_config).await {\n                                                    error!(\"{:#}\", err);\n                                                }\n                                            }.instrument(info_span!(\"connection\", %addr)));\n                                        }, Err(e) => {\n                                            error!(\"{:#}\", e);\n                                        }\n                                    }\n                                },\n                                Err(e) => {\n                                    error!(\"Transport handshake timeout: {}\", e);\n                                }\n                            }\n                        }\n                    }\n                },\n                // Wait for the shutdown signal\n                _ = shutdown_rx.recv() => {\n                    info!(\"Shuting down gracefully...\");\n                    break;\n                },\n                e = update_rx.recv() => {\n                    if let Some(e) = e {\n                        self.handle_hot_reload(e).await;\n                    }\n                }\n            }\n        }\n\n        info!(\"Shutdown\");\n\n        Ok(())\n    }\n\n    async fn handle_hot_reload(&mut self, e: ConfigChange) {\n        match e {\n            ConfigChange::ServerChange(server_change) => match server_change {\n                ServerServiceChange::Add(cfg) => {\n                    let hash = protocol::digest(cfg.name.as_bytes());\n                    let mut wg = self.services.write().await;\n                    let _ = wg.insert(hash, cfg);\n\n                    let mut wg = self.control_channels.write().await;\n                    let _ = wg.remove1(&hash);\n                }\n                ServerServiceChange::Delete(s) => {\n                    let hash = protocol::digest(s.as_bytes());\n                    let _ = self.services.write().await.remove(&hash);\n\n                    let mut wg = self.control_channels.write().await;\n                    let _ = wg.remove1(&hash);\n                }\n            },\n            ignored => warn!(\"Ignored {:?} since running as a server\", ignored),\n        }\n    }\n}\n\n// Handle connections to `server.bind_addr`\nasync fn handle_connection<T: 'static + Transport>(\n    mut conn: T::Stream,\n    services: Arc<RwLock<HashMap<ServiceDigest, ServerServiceConfig>>>,\n    control_channels: Arc<RwLock<ControlChannelMap<T>>>,\n    server_config: Arc<ServerConfig>,\n) -> Result<()> {\n    // Read hello\n    let hello = read_hello(&mut conn).await?;\n    match hello {\n        ControlChannelHello(_, service_digest) => {\n            do_control_channel_handshake(\n                conn,\n                services,\n                control_channels,\n                service_digest,\n                server_config,\n            )\n            .await?;\n        }\n        DataChannelHello(_, nonce) => {\n            do_data_channel_handshake(conn, control_channels, nonce).await?;\n        }\n    }\n    Ok(())\n}\n\nasync fn do_control_channel_handshake<T: 'static + Transport>(\n    mut conn: T::Stream,\n    services: Arc<RwLock<HashMap<ServiceDigest, ServerServiceConfig>>>,\n    control_channels: Arc<RwLock<ControlChannelMap<T>>>,\n    service_digest: ServiceDigest,\n    server_config: Arc<ServerConfig>,\n) -> Result<()> {\n    info!(\"Try to handshake a control channel\");\n\n    T::hint(&conn, SocketOpts::for_control_channel());\n\n    // Generate a nonce\n    let mut nonce = vec![0u8; HASH_WIDTH_IN_BYTES];\n    rand::thread_rng().fill_bytes(&mut nonce);\n\n    // Send hello\n    let hello_send = Hello::ControlChannelHello(\n        protocol::CURRENT_PROTO_VERSION,\n        nonce.clone().try_into().unwrap(),\n    );\n    conn.write_all(&bincode::serialize(&hello_send).unwrap())\n        .await?;\n    conn.flush().await?;\n\n    // Lookup the service\n    let service_config = match services.read().await.get(&service_digest) {\n        Some(v) => v,\n        None => {\n            conn.write_all(&bincode::serialize(&Ack::ServiceNotExist).unwrap())\n                .await?;\n            bail!(\"No such a service {}\", hex::encode(service_digest));\n        }\n    }\n    .to_owned();\n\n    let service_name = &service_config.name;\n\n    // Calculate the checksum\n    let mut concat = Vec::from(service_config.token.as_ref().unwrap().as_bytes());\n    concat.append(&mut nonce);\n\n    // Read auth\n    let protocol::Auth(d) = read_auth(&mut conn).await?;\n\n    // Validate\n    let session_key = protocol::digest(&concat);\n    if session_key != d {\n        conn.write_all(&bincode::serialize(&Ack::AuthFailed).unwrap())\n            .await?;\n        debug!(\n            \"Expect {}, but got {}\",\n            hex::encode(session_key),\n            hex::encode(d)\n        );\n        bail!(\"Service {} failed the authentication\", service_name);\n    } else {\n        let mut h = control_channels.write().await;\n\n        // If there's already a control channel for the service, then drop the old one.\n        // Because a control channel doesn't report back when it's dead,\n        // the handle in the map could be stall, dropping the old handle enables\n        // the client to reconnect.\n        if h.remove1(&service_digest).is_some() {\n            warn!(\n                \"Dropping previous control channel for service {}\",\n                service_name\n            );\n        }\n\n        // Send ack\n        conn.write_all(&bincode::serialize(&Ack::Ok).unwrap())\n            .await?;\n        conn.flush().await?;\n\n        info!(service = %service_config.name, \"Control channel established\");\n        let handle =\n            ControlChannelHandle::new(conn, service_config, server_config.heartbeat_interval);\n\n        // Insert the new handle\n        let _ = h.insert(service_digest, session_key, handle);\n    }\n\n    Ok(())\n}\n\nasync fn do_data_channel_handshake<T: 'static + Transport>(\n    conn: T::Stream,\n    control_channels: Arc<RwLock<ControlChannelMap<T>>>,\n    nonce: Nonce,\n) -> Result<()> {\n    debug!(\"Try to handshake a data channel\");\n\n    // Validate\n    let control_channels_guard = control_channels.read().await;\n    match control_channels_guard.get2(&nonce) {\n        Some(handle) => {\n            T::hint(&conn, SocketOpts::from_server_cfg(&handle.service));\n\n            // Send the data channel to the corresponding control channel\n            handle\n                .data_ch_tx\n                .send(conn)\n                .await\n                .with_context(|| \"Data channel for a stale control channel\")?;\n        }\n        None => {\n            warn!(\"Data channel has incorrect nonce\");\n        }\n    }\n    Ok(())\n}\n\npub struct ControlChannelHandle<T: Transport> {\n    // Shutdown the control channel by dropping it\n    _shutdown_tx: broadcast::Sender<bool>,\n    data_ch_tx: mpsc::Sender<T::Stream>,\n    service: ServerServiceConfig,\n}\n\nimpl<T> ControlChannelHandle<T>\nwhere\n    T: 'static + Transport,\n{\n    // Create a control channel handle, where the control channel handling task\n    // and the connection pool task are created.\n    #[instrument(name = \"handle\", skip_all, fields(service = %service.name))]\n    fn new(\n        conn: T::Stream,\n        service: ServerServiceConfig,\n        heartbeat_interval: u64,\n    ) -> ControlChannelHandle<T> {\n        // Create a shutdown channel\n        let (shutdown_tx, shutdown_rx) = broadcast::channel::<bool>(1);\n\n        // Store data channels\n        let (data_ch_tx, data_ch_rx) = mpsc::channel(CHAN_SIZE * 2);\n\n        // Store data channel creation requests\n        let (data_ch_req_tx, data_ch_req_rx) = mpsc::unbounded_channel();\n\n        // Cache some data channels for later use\n        let pool_size = match service.service_type {\n            ServiceType::Tcp => TCP_POOL_SIZE,\n            ServiceType::Udp => UDP_POOL_SIZE,\n        };\n\n        for _i in 0..pool_size {\n            if let Err(e) = data_ch_req_tx.send(true) {\n                error!(\"Failed to request data channel {}\", e);\n            };\n        }\n\n        let shutdown_rx_clone = shutdown_tx.subscribe();\n        let bind_addr = service.bind_addr.clone();\n        match service.service_type {\n            ServiceType::Tcp => tokio::spawn(\n                async move {\n                    if let Err(e) = run_tcp_connection_pool::<T>(\n                        bind_addr,\n                        data_ch_rx,\n                        data_ch_req_tx,\n                        shutdown_rx_clone,\n                    )\n                    .await\n                    .with_context(|| \"Failed to run TCP connection pool\")\n                    {\n                        error!(\"{:#}\", e);\n                    }\n                }\n                .instrument(Span::current()),\n            ),\n            ServiceType::Udp => tokio::spawn(\n                async move {\n                    if let Err(e) = run_udp_connection_pool::<T>(\n                        bind_addr,\n                        data_ch_rx,\n                        data_ch_req_tx,\n                        shutdown_rx_clone,\n                    )\n                    .await\n                    .with_context(|| \"Failed to run TCP connection pool\")\n                    {\n                        error!(\"{:#}\", e);\n                    }\n                }\n                .instrument(Span::current()),\n            ),\n        };\n\n        // Create the control channel\n        let ch = ControlChannel::<T> {\n            conn,\n            shutdown_rx,\n            data_ch_req_rx,\n            heartbeat_interval,\n        };\n\n        // Run the control channel\n        tokio::spawn(\n            async move {\n                if let Err(err) = ch.run().await {\n                    error!(\"{:#}\", err);\n                }\n            }\n            .instrument(Span::current()),\n        );\n\n        ControlChannelHandle {\n            _shutdown_tx: shutdown_tx,\n            data_ch_tx,\n            service,\n        }\n    }\n}\n\n// Control channel, using T as the transport layer. P is TcpStream or UdpTraffic\nstruct ControlChannel<T: Transport> {\n    conn: T::Stream,                               // The connection of control channel\n    shutdown_rx: broadcast::Receiver<bool>,        // Receives the shutdown signal\n    data_ch_req_rx: mpsc::UnboundedReceiver<bool>, // Receives visitor connections\n    heartbeat_interval: u64,                       // Application-layer heartbeat interval in secs\n}\n\nimpl<T: Transport> ControlChannel<T> {\n    async fn write_and_flush(&mut self, data: &[u8]) -> Result<()> {\n        write_and_flush(&mut self.conn, data)\n            .await\n            .with_context(|| \"Failed to write control cmds\")?;\n        Ok(())\n    }\n    // Run a control channel\n    #[instrument(skip_all)]\n    async fn run(mut self) -> Result<()> {\n        let create_ch_cmd = bincode::serialize(&ControlChannelCmd::CreateDataChannel).unwrap();\n        let heartbeat = bincode::serialize(&ControlChannelCmd::HeartBeat).unwrap();\n\n        // Wait for data channel requests and the shutdown signal\n        loop {\n            tokio::select! {\n                val = self.data_ch_req_rx.recv() => {\n                    match val {\n                        Some(_) => {\n                            if let Err(e) = self.write_and_flush(&create_ch_cmd).await {\n                                error!(\"{:#}\", e);\n                                break;\n                            }\n                        }\n                        None => {\n                            break;\n                        }\n                    }\n                },\n                _ = time::sleep(Duration::from_secs(self.heartbeat_interval)), if self.heartbeat_interval != 0 => {\n                            if let Err(e) = self.write_and_flush(&heartbeat).await {\n                                error!(\"{:#}\", e);\n                                break;\n                            }\n                }\n                // Wait for the shutdown signal\n                _ = self.shutdown_rx.recv() => {\n                    break;\n                }\n            }\n        }\n\n        info!(\"Control channel shutdown\");\n\n        Ok(())\n    }\n}\n\nfn tcp_listen_and_send(\n    addr: String,\n    data_ch_req_tx: mpsc::UnboundedSender<bool>,\n    mut shutdown_rx: broadcast::Receiver<bool>,\n) -> mpsc::Receiver<TcpStream> {\n    let (tx, rx) = mpsc::channel(CHAN_SIZE);\n\n    tokio::spawn(async move {\n        let l = retry_notify_with_deadline(listen_backoff(),  || async {\n            Ok(TcpListener::bind(&addr).await?)\n        }, |e, duration| {\n            error!(\"{:#}. Retry in {:?}\", e, duration);\n        }, &mut shutdown_rx).await\n        .with_context(|| \"Failed to listen for the service\");\n\n        let l: TcpListener = match l {\n            Ok(v) => v,\n            Err(e) => {\n                error!(\"{:#}\", e);\n                return;\n            }\n        };\n\n        info!(\"Listening at {}\", &addr);\n\n        // Retry at least every 1s\n        let mut backoff = ExponentialBackoff {\n            max_interval: Duration::from_secs(1),\n            max_elapsed_time: None,\n            ..Default::default()\n        };\n\n        // Wait for visitors and the shutdown signal\n        loop {\n            tokio::select! {\n                val = l.accept() => {\n                    match val {\n                        Err(e) => {\n                            // `l` is a TCP listener so this must be a IO error\n                            // Possibly a EMFILE. So sleep for a while\n                            error!(\"{}. Sleep for a while\", e);\n                            if let Some(d) = backoff.next_backoff() {\n                                time::sleep(d).await;\n                            } else {\n                                // This branch will never be reached for current backoff policy\n                                error!(\"Too many retries. Aborting...\");\n                                break;\n                            }\n                        }\n                        Ok((incoming, addr)) => {\n                            // For every visitor, request to create a data channel\n                            if data_ch_req_tx.send(true).with_context(|| \"Failed to send data chan create request\").is_err() {\n                                // An error indicates the control channel is broken\n                                // So break the loop\n                                break;\n                            }\n\n                            backoff.reset();\n\n                            debug!(\"New visitor from {}\", addr);\n\n                            // Send the visitor to the connection pool\n                            let _ = tx.send(incoming).await;\n                        }\n                    }\n                },\n                _ = shutdown_rx.recv() => {\n                    break;\n                }\n            }\n        }\n\n        info!(\"TCPListener shutdown\");\n    }.instrument(Span::current()));\n\n    rx\n}\n\n#[instrument(skip_all)]\nasync fn run_tcp_connection_pool<T: Transport>(\n    bind_addr: String,\n    mut data_ch_rx: mpsc::Receiver<T::Stream>,\n    data_ch_req_tx: mpsc::UnboundedSender<bool>,\n    shutdown_rx: broadcast::Receiver<bool>,\n) -> Result<()> {\n    let mut visitor_rx = tcp_listen_and_send(bind_addr, data_ch_req_tx.clone(), shutdown_rx);\n    let cmd = bincode::serialize(&DataChannelCmd::StartForwardTcp).unwrap();\n\n    'pool: while let Some(mut visitor) = visitor_rx.recv().await {\n        loop {\n            if let Some(mut ch) = data_ch_rx.recv().await {\n                if write_and_flush(&mut ch, &cmd).await.is_ok() {\n                    tokio::spawn(async move {\n                        let _ = copy_bidirectional(&mut ch, &mut visitor).await;\n                    });\n                    break;\n                } else {\n                    // Current data channel is broken. Request for a new one\n                    if data_ch_req_tx.send(true).is_err() {\n                        break 'pool;\n                    }\n                }\n            } else {\n                break 'pool;\n            }\n        }\n    }\n\n    info!(\"Shutdown\");\n    Ok(())\n}\n\n#[instrument(skip_all)]\nasync fn run_udp_connection_pool<T: Transport>(\n    bind_addr: String,\n    mut data_ch_rx: mpsc::Receiver<T::Stream>,\n    _data_ch_req_tx: mpsc::UnboundedSender<bool>,\n    mut shutdown_rx: broadcast::Receiver<bool>,\n) -> Result<()> {\n    // TODO: Load balance\n\n    let l = retry_notify_with_deadline(\n        listen_backoff(),\n        || async { Ok(UdpSocket::bind(&bind_addr).await?) },\n        |e, duration| {\n            warn!(\"{:#}. Retry in {:?}\", e, duration);\n        },\n        &mut shutdown_rx,\n    )\n    .await\n    .with_context(|| \"Failed to listen for the service\")?;\n\n    info!(\"Listening at {}\", &bind_addr);\n\n    let cmd = bincode::serialize(&DataChannelCmd::StartForwardUdp).unwrap();\n\n    // Receive one data channel\n    let mut conn = data_ch_rx\n        .recv()\n        .await\n        .ok_or_else(|| anyhow!(\"No available data channels\"))?;\n    write_and_flush(&mut conn, &cmd).await?;\n\n    let mut buf = [0u8; UDP_BUFFER_SIZE];\n    loop {\n        tokio::select! {\n            // Forward inbound traffic to the client\n            val = l.recv_from(&mut buf) => {\n                let (n, from) = val?;\n                UdpTraffic::write_slice(&mut conn, from, &buf[..n]).await?;\n            },\n\n            // Forward outbound traffic from the client to the visitor\n            hdr_len = conn.read_u8() => {\n                let t = UdpTraffic::read(&mut conn, hdr_len?).await?;\n                l.send_to(&t.data, t.from).await?;\n            }\n\n            _ = shutdown_rx.recv() => {\n                break;\n            }\n        }\n    }\n\n    debug!(\"UDP pool dropped\");\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/transport/mod.rs",
    "content": "use crate::config::{ClientServiceConfig, ServerServiceConfig, TcpConfig, TransportConfig};\nuse crate::helper::{to_socket_addr, try_set_tcp_keepalive};\nuse anyhow::{Context, Result};\nuse async_trait::async_trait;\nuse std::fmt::{Debug, Display};\nuse std::net::SocketAddr;\nuse std::time::Duration;\nuse tokio::io::{AsyncRead, AsyncWrite};\nuse tokio::net::{TcpStream, ToSocketAddrs};\nuse tracing::{error, trace};\n\npub const DEFAULT_NODELAY: bool = true;\n\npub const DEFAULT_KEEPALIVE_SECS: u64 = 20;\npub const DEFAULT_KEEPALIVE_INTERVAL: u64 = 8;\n\n#[derive(Clone)]\npub struct AddrMaybeCached {\n    pub addr: String,\n    pub socket_addr: Option<SocketAddr>,\n}\n\nimpl AddrMaybeCached {\n    pub fn new(addr: &str) -> AddrMaybeCached {\n        AddrMaybeCached {\n            addr: addr.to_string(),\n            socket_addr: None,\n        }\n    }\n\n    pub async fn resolve(&mut self) -> Result<()> {\n        match to_socket_addr(&self.addr).await {\n            Ok(s) => {\n                self.socket_addr = Some(s);\n                Ok(())\n            }\n            Err(e) => Err(e),\n        }\n    }\n}\n\nimpl Display for AddrMaybeCached {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self.socket_addr {\n            Some(s) => f.write_fmt(format_args!(\"{}\", s)),\n            None => f.write_str(&self.addr),\n        }\n    }\n}\n\n/// Specify a transport layer, like TCP, TLS\n#[async_trait]\npub trait Transport: Debug + Send + Sync {\n    type Acceptor: Send + Sync;\n    type RawStream: Send + Sync;\n    type Stream: 'static + AsyncRead + AsyncWrite + Unpin + Send + Sync + Debug;\n\n    fn new(config: &TransportConfig) -> Result<Self>\n    where\n        Self: Sized;\n    /// Provide the transport with socket options, which can be handled at the need of the transport\n    fn hint(conn: &Self::Stream, opts: SocketOpts);\n    async fn bind<T: ToSocketAddrs + Send + Sync>(&self, addr: T) -> Result<Self::Acceptor>;\n    /// accept must be cancel safe\n    async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)>;\n    async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream>;\n    async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream>;\n}\n\nmod tcp;\npub use tcp::TcpTransport;\n\n#[cfg(all(feature = \"native-tls\", feature = \"rustls\"))]\ncompile_error!(\"Only one of `native-tls` and `rustls` can be enabled\");\n\n#[cfg(feature = \"native-tls\")]\nmod native_tls;\n#[cfg(feature = \"native-tls\")]\nuse native_tls as tls;\n#[cfg(feature = \"rustls\")]\nmod rustls;\n#[cfg(feature = \"rustls\")]\nuse rustls as tls;\n\n#[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\npub(crate) use tls::TlsTransport;\n\n#[cfg(feature = \"noise\")]\nmod noise;\n#[cfg(feature = \"noise\")]\npub use noise::NoiseTransport;\n\n#[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\nmod websocket;\n#[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\npub use websocket::WebsocketTransport;\n\n#[derive(Debug, Clone, Copy)]\nstruct Keepalive {\n    // tcp_keepalive_time if the underlying protocol is TCP\n    pub keepalive_secs: u64,\n    // tcp_keepalive_intvl if the underlying protocol is TCP\n    pub keepalive_interval: u64,\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct SocketOpts {\n    // None means do not change\n    nodelay: Option<bool>,\n    // keepalive must be Some or None at the same time, or the behavior will be platform-dependent\n    keepalive: Option<Keepalive>,\n}\n\nimpl SocketOpts {\n    fn none() -> SocketOpts {\n        SocketOpts {\n            nodelay: None,\n            keepalive: None,\n        }\n    }\n\n    /// Socket options for the control channel\n    pub fn for_control_channel() -> SocketOpts {\n        SocketOpts {\n            nodelay: Some(true),  // Always set nodelay for the control channel\n            ..SocketOpts::none()  // None means do not change. Keepalive is set by TcpTransport\n        }\n    }\n}\n\nimpl SocketOpts {\n    pub fn from_cfg(cfg: &TcpConfig) -> SocketOpts {\n        SocketOpts {\n            nodelay: Some(cfg.nodelay),\n            keepalive: Some(Keepalive {\n                keepalive_secs: cfg.keepalive_secs,\n                keepalive_interval: cfg.keepalive_interval,\n            }),\n        }\n    }\n\n    pub fn from_client_cfg(cfg: &ClientServiceConfig) -> SocketOpts {\n        SocketOpts {\n            nodelay: cfg.nodelay,\n            ..SocketOpts::none()\n        }\n    }\n\n    pub fn from_server_cfg(cfg: &ServerServiceConfig) -> SocketOpts {\n        SocketOpts {\n            nodelay: cfg.nodelay,\n            ..SocketOpts::none()\n        }\n    }\n\n    pub fn apply(&self, conn: &TcpStream) {\n        if let Some(v) = self.keepalive {\n            let keepalive_duration = Duration::from_secs(v.keepalive_secs);\n            let keepalive_interval = Duration::from_secs(v.keepalive_interval);\n\n            if let Err(e) = try_set_tcp_keepalive(conn, keepalive_duration, keepalive_interval)\n                .with_context(|| \"Failed to set keepalive\")\n            {\n                error!(\"{:#}\", e);\n            }\n        }\n\n        if let Some(nodelay) = self.nodelay {\n            trace!(\"Set nodelay {}\", nodelay);\n            if let Err(e) = conn\n                .set_nodelay(nodelay)\n                .with_context(|| \"Failed to set nodelay\")\n            {\n                error!(\"{:#}\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/transport/native_tls.rs",
    "content": "use crate::config::{TlsConfig, TransportConfig};\nuse crate::helper::host_port_pair;\nuse crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};\nuse anyhow::{anyhow, Context, Result};\nuse async_trait::async_trait;\nuse std::fs;\nuse std::net::SocketAddr;\nuse tokio::net::{TcpListener, TcpStream, ToSocketAddrs};\nuse tokio_native_tls::native_tls::{self, Certificate, Identity};\npub(crate) use tokio_native_tls::TlsStream;\nuse tokio_native_tls::{TlsAcceptor, TlsConnector};\n\n#[derive(Debug)]\npub struct TlsTransport {\n    tcp: TcpTransport,\n    config: TlsConfig,\n    connector: Option<TlsConnector>,\n    tls_acceptor: Option<TlsAcceptor>,\n}\n\n#[async_trait]\nimpl Transport for TlsTransport {\n    type Acceptor = TcpListener;\n    type RawStream = TcpStream;\n    type Stream = TlsStream<TcpStream>;\n\n    fn new(config: &TransportConfig) -> Result<Self> {\n        let tcp = TcpTransport::new(config)?;\n        let config = config\n            .tls\n            .as_ref()\n            .ok_or_else(|| anyhow!(\"Missing tls config\"))?;\n\n        let connector = match config.trusted_root.as_ref() {\n            Some(path) => {\n                let s = fs::read_to_string(path)\n                    .with_context(|| \"Failed to read the `tls.trusted_root`\")?;\n                let cert = Certificate::from_pem(s.as_bytes())\n                    .with_context(|| \"Failed to read certificate from `tls.trusted_root`\")?;\n                let connector = native_tls::TlsConnector::builder()\n                    .add_root_certificate(cert)\n                    .build()?;\n                Some(TlsConnector::from(connector))\n            }\n            None => {\n                // if no trusted_root is specified, allow TlsConnector to use system default\n                let connector = native_tls::TlsConnector::builder().build()?;\n                Some(TlsConnector::from(connector))\n            }\n        };\n\n        let tls_acceptor = match config.pkcs12.as_ref() {\n            Some(path) => {\n                let ident = Identity::from_pkcs12(\n                    &fs::read(path)?,\n                    config.pkcs12_password.as_ref().unwrap(),\n                )\n                .with_context(|| \"Failed to create identitiy\")?;\n                Some(TlsAcceptor::from(\n                    native_tls::TlsAcceptor::new(ident).unwrap(),\n                ))\n            }\n            None => None,\n        };\n\n        Ok(TlsTransport {\n            tcp,\n            config: config.clone(),\n            connector,\n            tls_acceptor,\n        })\n    }\n\n    fn hint(conn: &Self::Stream, opt: SocketOpts) {\n        opt.apply(conn.get_ref().get_ref().get_ref());\n    }\n\n    async fn bind<A: ToSocketAddrs + Send + Sync>(&self, addr: A) -> Result<Self::Acceptor> {\n        let l = TcpListener::bind(addr)\n            .await\n            .with_context(|| \"Failed to create tcp listener\")?;\n        Ok(l)\n    }\n\n    async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> {\n        self.tcp\n            .accept(a)\n            .await\n            .with_context(|| \"Failed to accept TCP connection\")\n    }\n\n    async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream> {\n        let conn = self.tls_acceptor.as_ref().unwrap().accept(conn).await?;\n        Ok(conn)\n    }\n\n    async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {\n        let conn = self.tcp.connect(addr).await?;\n\n        let connector = self.connector.as_ref().unwrap();\n        Ok(connector\n            .connect(\n                self.config\n                    .hostname\n                    .as_deref()\n                    .unwrap_or(host_port_pair(&addr.addr)?.0),\n                conn,\n            )\n            .await?)\n    }\n}\n\n#[cfg(feature = \"websocket-native-tls\")]\npub(crate) fn get_tcpstream(s: &TlsStream<TcpStream>) -> &TcpStream {\n    s.get_ref().get_ref().get_ref()\n}\n"
  },
  {
    "path": "src/transport/noise.rs",
    "content": "use std::net::SocketAddr;\n\nuse super::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};\nuse crate::config::{NoiseConfig, TransportConfig};\nuse anyhow::{anyhow, Context, Result};\nuse async_trait::async_trait;\nuse snowstorm::{Builder, NoiseParams, NoiseStream};\nuse tokio::net::{TcpListener, TcpStream, ToSocketAddrs};\n\npub struct NoiseTransport {\n    tcp: TcpTransport,\n    config: NoiseConfig,\n    params: NoiseParams,\n    local_private_key: Vec<u8>,\n    remote_public_key: Option<Vec<u8>>,\n}\n\nimpl std::fmt::Debug for NoiseTransport {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {\n        write!(f, \"{:?}\", self.config)\n    }\n}\n\nimpl NoiseTransport {\n    fn builder(&self) -> Builder {\n        let builder = Builder::new(self.params.clone()).local_private_key(&self.local_private_key);\n        match &self.remote_public_key {\n            Some(x) => builder.remote_public_key(x),\n            None => builder,\n        }\n    }\n}\n\n#[async_trait]\nimpl Transport for NoiseTransport {\n    type Acceptor = TcpListener;\n    type RawStream = TcpStream;\n    type Stream = snowstorm::stream::NoiseStream<TcpStream>;\n\n    fn new(config: &TransportConfig) -> Result<Self> {\n        let tcp = TcpTransport::new(config)?;\n\n        let config = match &config.noise {\n            Some(v) => v.clone(),\n            None => return Err(anyhow!(\"Missing noise config\")),\n        };\n        let builder = Builder::new(config.pattern.parse()?);\n\n        let remote_public_key = match &config.remote_public_key {\n            Some(x) => {\n                Some(base64::decode(x).with_context(|| \"Failed to decode remote_public_key\")?)\n            }\n            None => None,\n        };\n\n        let local_private_key = match &config.local_private_key {\n            Some(x) => base64::decode(x.as_bytes())\n                .with_context(|| \"Failed to decode local_private_key\")?,\n            None => builder.generate_keypair()?.private,\n        };\n\n        let params: NoiseParams = config.pattern.parse()?;\n\n        Ok(NoiseTransport {\n            tcp,\n            config,\n            params,\n            local_private_key,\n            remote_public_key,\n        })\n    }\n\n    fn hint(conn: &Self::Stream, opt: SocketOpts) {\n        opt.apply(conn.get_inner());\n    }\n\n    async fn bind<T: ToSocketAddrs + Send + Sync>(&self, addr: T) -> Result<Self::Acceptor> {\n        Ok(TcpListener::bind(addr).await?)\n    }\n\n    async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> {\n        self.tcp\n            .accept(a)\n            .await\n            .with_context(|| \"Failed to accept TCP connection\")\n    }\n\n    async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream> {\n        let conn = NoiseStream::handshake(conn, self.builder().build_responder()?)\n            .await\n            .with_context(|| \"Failed to do noise handshake\")?;\n        Ok(conn)\n    }\n\n    async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {\n        let conn = self\n            .tcp\n            .connect(addr)\n            .await\n            .with_context(|| \"Failed to connect TCP socket\")?;\n\n        let conn = NoiseStream::handshake(conn, self.builder().build_initiator()?)\n            .await\n            .with_context(|| \"Failed to do noise handshake\")?;\n        return Ok(conn);\n    }\n}\n"
  },
  {
    "path": "src/transport/rustls.rs",
    "content": "use crate::config::{TlsConfig, TransportConfig};\nuse crate::helper::host_port_pair;\nuse crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};\nuse std::fmt::Debug;\nuse std::fs;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse tokio::net::{TcpListener, TcpStream, ToSocketAddrs};\nuse tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName};\n\nuse anyhow::{anyhow, Context, Result};\nuse async_trait::async_trait;\nuse p12::PFX;\nuse tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};\npub(crate) use tokio_rustls::TlsStream;\nuse tokio_rustls::{TlsAcceptor, TlsConnector};\n\npub struct TlsTransport {\n    tcp: TcpTransport,\n    config: TlsConfig,\n    connector: Option<TlsConnector>,\n    tls_acceptor: Option<TlsAcceptor>,\n}\n\n// workaround for TlsConnector and TlsAcceptor not implementing Debug\nimpl Debug for TlsTransport {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"TlsTransport\")\n            .field(\"tcp\", &self.tcp)\n            .field(\"config\", &self.config)\n            .finish()\n    }\n}\n\nfn load_server_config(config: &TlsConfig) -> Result<Option<ServerConfig>> {\n    if let Some(pkcs12_path) = config.pkcs12.as_ref() {\n        let buf = fs::read(pkcs12_path)?;\n        let pfx = PFX::parse(buf.as_slice())?;\n        let pass = config.pkcs12_password.as_ref().unwrap();\n\n        let certs = pfx.cert_bags(pass)?;\n        let keys = pfx.key_bags(pass)?;\n\n        let chain: Vec<CertificateDer> = certs.into_iter().map(CertificateDer::from).collect();\n        let key = PrivatePkcs8KeyDer::from(keys.into_iter().next().unwrap());\n\n        Ok(Some(\n            ServerConfig::builder()\n                .with_no_client_auth()\n                .with_single_cert(chain, key.into())?,\n        ))\n    } else {\n        Ok(None)\n    }\n}\n\nfn load_client_config(config: &TlsConfig) -> Result<Option<ClientConfig>> {\n    let cert = if let Some(path) = config.trusted_root.as_ref() {\n        rustls_pemfile::certs(&mut std::io::BufReader::new(fs::File::open(path).unwrap()))\n            .map(|cert| cert.unwrap())\n            .next()\n            .with_context(|| \"Failed to read certificate\")?\n    } else {\n        // read from native\n        match rustls_native_certs::load_native_certs() {\n            Ok(certs) => certs.into_iter().next().unwrap(),\n            Err(e) => {\n                eprintln!(\"Failed to load native certs: {}\", e);\n                return Ok(None);\n            }\n        }\n    };\n\n    let mut root_certs = RootCertStore::empty();\n    root_certs.add(cert).unwrap();\n\n    Ok(Some(\n        ClientConfig::builder()\n            .with_root_certificates(root_certs)\n            .with_no_client_auth(),\n    ))\n}\n\n#[async_trait]\nimpl Transport for TlsTransport {\n    type Acceptor = TcpListener;\n    type RawStream = TcpStream;\n    type Stream = TlsStream<TcpStream>;\n\n    fn new(config: &TransportConfig) -> Result<Self> {\n        let tcp = TcpTransport::new(config)?;\n        let config = config\n            .tls\n            .as_ref()\n            .ok_or_else(|| anyhow!(\"Missing tls config\"))?;\n\n        let connector = load_client_config(config)\n            .unwrap()\n            .map(|c| Arc::new(c).into());\n        let tls_acceptor = load_server_config(config)\n            .unwrap()\n            .map(|c| Arc::new(c).into());\n\n        Ok(TlsTransport {\n            tcp,\n            config: config.clone(),\n            connector,\n            tls_acceptor,\n        })\n    }\n\n    fn hint(conn: &Self::Stream, opt: SocketOpts) {\n        opt.apply(conn.get_ref().0);\n    }\n\n    async fn bind<A: ToSocketAddrs + Send + Sync>(&self, addr: A) -> Result<Self::Acceptor> {\n        let l = TcpListener::bind(addr)\n            .await\n            .with_context(|| \"Failed to create tcp listener\")?;\n        Ok(l)\n    }\n\n    async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> {\n        self.tcp\n            .accept(a)\n            .await\n            .with_context(|| \"Failed to accept TCP connection\")\n    }\n\n    async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream> {\n        let conn = self.tls_acceptor.as_ref().unwrap().accept(conn).await?;\n        Ok(tokio_rustls::TlsStream::Server(conn))\n    }\n\n    async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {\n        let conn = self.tcp.connect(addr).await?;\n\n        let connector = self.connector.as_ref().unwrap();\n\n        let host_name = self\n            .config\n            .hostname\n            .as_deref()\n            .unwrap_or(host_port_pair(&addr.addr)?.0);\n\n        Ok(tokio_rustls::TlsStream::Client(\n            connector\n                .connect(ServerName::try_from(host_name)?.to_owned(), conn)\n                .await?,\n        ))\n    }\n}\n\npub(crate) fn get_tcpstream(s: &TlsStream<TcpStream>) -> &TcpStream {\n    &s.get_ref().0\n}\n"
  },
  {
    "path": "src/transport/tcp.rs",
    "content": "use crate::{\n    config::{TcpConfig, TransportConfig},\n    helper::tcp_connect_with_proxy,\n};\n\nuse super::{AddrMaybeCached, SocketOpts, Transport};\nuse anyhow::Result;\nuse async_trait::async_trait;\nuse std::net::SocketAddr;\nuse tokio::net::{TcpListener, TcpStream, ToSocketAddrs};\n\n#[derive(Debug)]\npub struct TcpTransport {\n    socket_opts: SocketOpts,\n    cfg: TcpConfig,\n}\n\n#[async_trait]\nimpl Transport for TcpTransport {\n    type Acceptor = TcpListener;\n    type Stream = TcpStream;\n    type RawStream = TcpStream;\n\n    fn new(config: &TransportConfig) -> Result<Self> {\n        Ok(TcpTransport {\n            socket_opts: SocketOpts::from_cfg(&config.tcp),\n            cfg: config.tcp.clone(),\n        })\n    }\n\n    fn hint(conn: &Self::Stream, opt: SocketOpts) {\n        opt.apply(conn);\n    }\n\n    async fn bind<T: ToSocketAddrs + Send + Sync>(&self, addr: T) -> Result<Self::Acceptor> {\n        Ok(TcpListener::bind(addr).await?)\n    }\n\n    async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> {\n        let (s, addr) = a.accept().await?;\n        self.socket_opts.apply(&s);\n        Ok((s, addr))\n    }\n\n    async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream> {\n        Ok(conn)\n    }\n\n    async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {\n        let s = tcp_connect_with_proxy(addr, self.cfg.proxy.as_ref()).await?;\n        self.socket_opts.apply(&s);\n        Ok(s)\n    }\n}\n"
  },
  {
    "path": "src/transport/websocket.rs",
    "content": "use core::result::Result;\nuse std::io::{Error, ErrorKind};\nuse std::net::SocketAddr;\nuse std::pin::Pin;\nuse std::task::{ready, Context, Poll};\n\nuse super::{AddrMaybeCached, SocketOpts, TcpTransport, TlsTransport, Transport};\nuse crate::config::TransportConfig;\nuse anyhow::anyhow;\nuse async_trait::async_trait;\nuse bytes::Bytes;\nuse futures_core::stream::Stream;\nuse futures_sink::Sink;\nuse tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};\nuse tokio::net::{TcpListener, TcpStream, ToSocketAddrs};\n\n#[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\nuse super::tls::get_tcpstream;\n#[cfg(any(feature = \"native-tls\", feature = \"rustls\"))]\nuse super::tls::TlsStream;\n\nuse tokio_tungstenite::tungstenite::protocol::{Message, WebSocketConfig};\nuse tokio_tungstenite::{accept_async_with_config, client_async_with_config, WebSocketStream};\nuse tokio_util::io::StreamReader;\nuse url::Url;\n\n#[derive(Debug)]\nenum TransportStream {\n    Insecure(TcpStream),\n    Secure(TlsStream<TcpStream>),\n}\n\nimpl TransportStream {\n    fn get_tcpstream(&self) -> &TcpStream {\n        match self {\n            TransportStream::Insecure(s) => s,\n            TransportStream::Secure(s) => get_tcpstream(s),\n        }\n    }\n}\n\nimpl AsyncRead for TransportStream {\n    fn poll_read(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &mut ReadBuf<'_>,\n    ) -> Poll<std::io::Result<()>> {\n        match self.get_mut() {\n            TransportStream::Insecure(s) => Pin::new(s).poll_read(cx, buf),\n            TransportStream::Secure(s) => Pin::new(s).poll_read(cx, buf),\n        }\n    }\n}\n\nimpl AsyncWrite for TransportStream {\n    fn poll_write(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<Result<usize, std::io::Error>> {\n        match self.get_mut() {\n            TransportStream::Insecure(s) => Pin::new(s).poll_write(cx, buf),\n            TransportStream::Secure(s) => Pin::new(s).poll_write(cx, buf),\n        }\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {\n        match self.get_mut() {\n            TransportStream::Insecure(s) => Pin::new(s).poll_flush(cx),\n            TransportStream::Secure(s) => Pin::new(s).poll_flush(cx),\n        }\n    }\n\n    fn poll_shutdown(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n    ) -> Poll<Result<(), std::io::Error>> {\n        match self.get_mut() {\n            TransportStream::Insecure(s) => Pin::new(s).poll_shutdown(cx),\n            TransportStream::Secure(s) => Pin::new(s).poll_shutdown(cx),\n        }\n    }\n}\n\n#[derive(Debug)]\nstruct StreamWrapper {\n    inner: WebSocketStream<TransportStream>,\n}\n\nimpl Stream for StreamWrapper {\n    type Item = Result<Bytes, Error>;\n\n    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        match Pin::new(&mut self.get_mut().inner).poll_next(cx) {\n            Poll::Pending => Poll::Pending,\n            Poll::Ready(None) => Poll::Ready(None),\n            Poll::Ready(Some(Err(err))) => {\n                Poll::Ready(Some(Err(Error::new(ErrorKind::Other, err))))\n            }\n            Poll::Ready(Some(Ok(res))) => {\n                if let Message::Binary(b) = res {\n                    Poll::Ready(Some(Ok(Bytes::from(b))))\n                } else {\n                    Poll::Ready(Some(Err(Error::new(\n                        ErrorKind::InvalidData,\n                        \"unexpected frame\",\n                    ))))\n                }\n            }\n        }\n    }\n\n    fn size_hint(&self) -> (usize, Option<usize>) {\n        self.inner.size_hint()\n    }\n}\n\n#[derive(Debug)]\npub struct WebsocketTunnel {\n    inner: StreamReader<StreamWrapper, Bytes>,\n}\n\nimpl AsyncRead for WebsocketTunnel {\n    fn poll_read(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &mut ReadBuf<'_>,\n    ) -> Poll<std::io::Result<()>> {\n        Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)\n    }\n}\n\nimpl AsyncBufRead for WebsocketTunnel {\n    fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<&[u8]>> {\n        Pin::new(&mut self.get_mut().inner).poll_fill_buf(cx)\n    }\n\n    fn consume(self: Pin<&mut Self>, amt: usize) {\n        Pin::new(&mut self.get_mut().inner).consume(amt)\n    }\n}\n\nimpl AsyncWrite for WebsocketTunnel {\n    fn poll_write(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<Result<usize, std::io::Error>> {\n        let sw = self.get_mut().inner.get_mut();\n        ready!(Pin::new(&mut sw.inner)\n            .poll_ready(cx)\n            .map_err(|err| Error::new(ErrorKind::Other, err)))?;\n\n        match Pin::new(&mut sw.inner).start_send(Message::Binary(buf.to_vec())) {\n            Ok(()) => Poll::Ready(Ok(buf.len())),\n            Err(e) => Poll::Ready(Err(Error::new(ErrorKind::Other, e))),\n        }\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {\n        Pin::new(&mut self.get_mut().inner.get_mut().inner)\n            .poll_flush(cx)\n            .map_err(|err| Error::new(ErrorKind::Other, err))\n    }\n\n    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {\n        Pin::new(&mut self.get_mut().inner.get_mut().inner)\n            .poll_close(cx)\n            .map_err(|err| Error::new(ErrorKind::Other, err))\n    }\n}\n\n#[derive(Debug)]\nenum SubTransport {\n    Secure(TlsTransport),\n    Insecure(TcpTransport),\n}\n\n#[derive(Debug)]\npub struct WebsocketTransport {\n    sub: SubTransport,\n    conf: WebSocketConfig,\n}\n\n#[async_trait]\nimpl Transport for WebsocketTransport {\n    type Acceptor = TcpListener;\n    type RawStream = TcpStream;\n    type Stream = WebsocketTunnel;\n\n    fn new(config: &TransportConfig) -> anyhow::Result<Self> {\n        let wsconfig = config\n            .websocket\n            .as_ref()\n            .ok_or_else(|| anyhow!(\"Missing websocket config\"))?;\n\n        let conf = WebSocketConfig {\n            write_buffer_size: 0,\n            ..WebSocketConfig::default()\n        };\n        let sub = match wsconfig.tls {\n            true => SubTransport::Secure(TlsTransport::new(config)?),\n            false => SubTransport::Insecure(TcpTransport::new(config)?),\n        };\n        Ok(WebsocketTransport { sub, conf })\n    }\n\n    fn hint(conn: &Self::Stream, opt: SocketOpts) {\n        opt.apply(conn.inner.get_ref().inner.get_ref().get_tcpstream())\n    }\n\n    async fn bind<A: ToSocketAddrs + Send + Sync>(\n        &self,\n        addr: A,\n    ) -> anyhow::Result<Self::Acceptor> {\n        TcpListener::bind(addr).await.map_err(Into::into)\n    }\n\n    async fn accept(&self, a: &Self::Acceptor) -> anyhow::Result<(Self::RawStream, SocketAddr)> {\n        let (s, addr) = match &self.sub {\n            SubTransport::Insecure(t) => t.accept(a).await?,\n            SubTransport::Secure(t) => t.accept(a).await?,\n        };\n        Ok((s, addr))\n    }\n\n    async fn handshake(&self, conn: Self::RawStream) -> anyhow::Result<Self::Stream> {\n        let tsream = match &self.sub {\n            SubTransport::Insecure(t) => TransportStream::Insecure(t.handshake(conn).await?),\n            SubTransport::Secure(t) => TransportStream::Secure(t.handshake(conn).await?),\n        };\n        let wsstream = accept_async_with_config(tsream, Some(self.conf)).await?;\n        let tun = WebsocketTunnel {\n            inner: StreamReader::new(StreamWrapper { inner: wsstream }),\n        };\n        Ok(tun)\n    }\n\n    async fn connect(&self, addr: &AddrMaybeCached) -> anyhow::Result<Self::Stream> {\n        let u = format!(\"ws://{}\", &addr.addr.as_str());\n        let url = Url::parse(&u).unwrap();\n        let tstream = match &self.sub {\n            SubTransport::Insecure(t) => TransportStream::Insecure(t.connect(addr).await?),\n            SubTransport::Secure(t) => TransportStream::Secure(t.connect(addr).await?),\n        };\n        let (wsstream, _) = client_async_with_config(url, tstream, Some(self.conf))\n            .await\n            .expect(\"failed to connect\");\n        let tun = WebsocketTunnel {\n            inner: StreamReader::new(StreamWrapper { inner: wsstream }),\n        };\n        Ok(tun)\n    }\n}\n"
  },
  {
    "path": "tests/common/mod.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::Result;\nuse tokio::{\n    io::{self, AsyncReadExt, AsyncWriteExt},\n    net::{TcpListener, TcpStream, ToSocketAddrs},\n    sync::broadcast,\n};\n\npub const PING: &str = \"ping\";\npub const PONG: &str = \"pong\";\n\npub async fn run_rathole_server(\n    config_path: &str,\n    shutdown_rx: broadcast::Receiver<bool>,\n) -> Result<()> {\n    let cli = rathole::Cli {\n        config_path: Some(PathBuf::from(config_path)),\n        server: true,\n        client: false,\n        ..Default::default()\n    };\n    rathole::run(cli, shutdown_rx).await\n}\n\npub async fn run_rathole_client(\n    config_path: &str,\n    shutdown_rx: broadcast::Receiver<bool>,\n) -> Result<()> {\n    let cli = rathole::Cli {\n        config_path: Some(PathBuf::from(config_path)),\n        server: false,\n        client: true,\n        ..Default::default()\n    };\n    rathole::run(cli, shutdown_rx).await\n}\n\npub mod tcp {\n    use super::*;\n\n    pub async fn echo_server<A: ToSocketAddrs>(addr: A) -> Result<()> {\n        let l = TcpListener::bind(addr).await?;\n\n        loop {\n            let (conn, _addr) = l.accept().await?;\n            tokio::spawn(async move {\n                let _ = echo(conn).await;\n            });\n        }\n    }\n\n    pub async fn pingpong_server<A: ToSocketAddrs>(addr: A) -> Result<()> {\n        let l = TcpListener::bind(addr).await?;\n\n        loop {\n            let (conn, _addr) = l.accept().await?;\n            tokio::spawn(async move {\n                let _ = pingpong(conn).await;\n            });\n        }\n    }\n\n    async fn echo(conn: TcpStream) -> Result<()> {\n        let (mut rd, mut wr) = conn.into_split();\n        io::copy(&mut rd, &mut wr).await?;\n\n        Ok(())\n    }\n\n    async fn pingpong(mut conn: TcpStream) -> Result<()> {\n        let mut buf = [0u8; PING.len()];\n\n        while conn.read_exact(&mut buf).await? != 0 {\n            assert_eq!(buf, PING.as_bytes());\n            conn.write_all(PONG.as_bytes()).await?;\n        }\n\n        Ok(())\n    }\n}\n\npub mod udp {\n    use rathole::UDP_BUFFER_SIZE;\n    use tokio::net::UdpSocket;\n    use tracing::debug;\n\n    use super::*;\n\n    pub async fn echo_server<A: ToSocketAddrs>(addr: A) -> Result<()> {\n        let l = UdpSocket::bind(addr).await?;\n        debug!(\"UDP echo server listening\");\n\n        let mut buf = [0u8; UDP_BUFFER_SIZE];\n        loop {\n            let (n, addr) = l.recv_from(&mut buf).await?;\n            debug!(\"Get {:?} from {}\", &buf[..n], addr);\n            l.send_to(&buf[..n], addr).await?;\n        }\n    }\n\n    pub async fn pingpong_server<A: ToSocketAddrs>(addr: A) -> Result<()> {\n        let l = UdpSocket::bind(addr).await?;\n\n        let mut buf = [0u8; UDP_BUFFER_SIZE];\n        loop {\n            let (n, addr) = l.recv_from(&mut buf).await?;\n            assert_eq!(&buf[..n], PING.as_bytes());\n            l.send_to(PONG.as_bytes(), addr).await?;\n        }\n    }\n}\n"
  },
  {
    "path": "tests/config_test/invalid_config/missing_tls_client.toml",
    "content": "[client]\nremote_addr = \"example.com:2333\"\n\n[client.transport]\ntype = \"tls\" \n\n[client.services.service1] \ntoken = \"whatever\" \nlocal_addr = \"127.0.0.1:1081\" \n"
  },
  {
    "path": "tests/config_test/invalid_config/missing_tls_server.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\" \n\n[server.transport]\ntype = \"tls\" \n[server.transport.tls] \npkcs12_password = \"password\" \n\n[server.services.service1] \ntoken = \"whatever\" \nbind_addr = \"0.0.0.0:8081\" \n"
  },
  {
    "path": "tests/config_test/invalid_config/missing_tls_server2.toml",
    "content": "[server]\nbind_addr = \"0.0.0.0:2333\" \n\n[server.transport]\ntype = \"tls\" \n\n[server.services.service1] \ntoken = \"whatever\" \nbind_addr = \"0.0.0.0:8081\" \n"
  },
  {
    "path": "tests/config_test/valid_config/full.toml",
    "content": "[client]\nremote_addr = \"example.com:2333\" # Necessary. The address of the server\ndefault_token = \"default_token_if_not_specify\" # Optional. The default token of services, if they don't define their own ones\n\n[client.transport]\ntype = \"tcp\" # Optional. Possible values: [\"tcp\", \"tls\"]. Default: \"tcp\"\n\n[client.transport.tls] # Necessary if `type` is \"tls\"\ntrusted_root = \"ca.pem\" # Necessary. The certificate of CA that signed the server's certificate\nhostname = \"example.com\" # Optional. The hostname that the client uses to validate the certificate. If not set, fallback to `client.remote_addr`\n\n[client.transport.noise] # Noise protocol. See `docs/transport.md` for further explanation\npattern = \"Noise_NK_25519_ChaChaPoly_BLAKE2s\" # Optional. Default value as shown\nlocal_private_key = \"key_encoded_in_base64\" # Optional\nremote_public_key = \"key_encoded_in_base64\" # Optional\n\n[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration\ntype = \"tcp\" # Optional. The protocol that needs forwarding. Possible values: [\"tcp\", \"udp\"]. Default: \"tcp\"\ntoken = \"whatever\" # Necessary if `client.default_token` not set\nprefer_ipv6 = false # Optional. If the client prefers to use IPv6 when connecting to the server (e.g.: When the client is behind an ISP's NAT). Default: false\nlocal_addr = \"127.0.0.1:1081\" # Necessary. The address of the service that needs to be forwarded\n\n[client.services.service2] # Multiple services can be defined\nlocal_addr = \"127.0.0.1:1082\"\n\n[server]\nbind_addr = \"0.0.0.0:2333\" # Necessary. The address that the server listens for clients. Generally only the port needs to be change. \ndefault_token = \"default_token_if_not_specify\" # Optional\n\n[server.transport]\ntype = \"tcp\" # Same as `[client.transport]`\n\n[server.transport.tls] # Necessary if `type` is \"tls\"\npkcs12 = \"identify.pfx\" # Necessary. pkcs12 file of server's certificate and private key\npkcs12_password = \"password\" # Necessary. Password of the pkcs12 file\n\n[server.transport.noise] # Same as `[client.transport.noise]`\npattern = \"Noise_NK_25519_ChaChaPoly_BLAKE2s\"\nlocal_private_key = \"key_encoded_in_base64\" \nremote_public_key = \"key_encoded_in_base64\" \n\n[server.services.service1] # The service name must be identical to the client side\ntype = \"tcp\" # Optional. Same as the client `[client.services.X.type]\ntoken = \"whatever\" # Necesary if `server.default_token` not set\nbind_addr = \"0.0.0.0:8081\" # Necessary. The address of the service is exposed at. Generally only the port needs to be change. \n\n[server.services.service2] \nbind_addr = \"0.0.0.1:8082\"\n"
  },
  {
    "path": "tests/for_tcp/noise_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"noise\" \n[client.transport.noise]\nremote_public_key = \"mEnUEACy9UrTBmwoCJb6fcKWBRdvfD9XzuBVsroOLFg=\"\n\n[client.services.echo] \nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"noise\" \n[server.transport.noise]\nlocal_private_key = \"kQiSRtS3bs8BoGCJYgFnl1FLrTG1lV53Dj8jSjmg8tE=\"\n\n[server.services.echo] \nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_tcp/tcp_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"tcp\" \n\n[client.services.echo] \nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"tcp\" \n\n[server.services.echo] \nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_tcp/tls_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"tls\" \n[client.transport.tls]\ntrusted_root = \"examples/tls/rootCA.crt\"\nhostname = \"localhost\"\n\n[client.services.echo] \nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"tls\" \n[server.transport.tls]\npkcs12 = \"examples/tls/identity.pfx\"\npkcs12_password = \"1234\"\n\n[server.services.echo] \nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_tcp/websocket_tls_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"websocket\" \n[client.transport.tls]\ntrusted_root = \"examples/tls/rootCA.crt\"\nhostname = \"localhost\"\n[client.transport.websocket] \ntls = true\n\n[client.services.echo] \nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"websocket\" \n[server.transport.tls]\npkcs12 = \"examples/tls/identity.pfx\"\npkcs12_password = \"1234\"\n[server.transport.websocket] \ntls = true\n\n[server.services.echo] \nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_tcp/websocket_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"websocket\" \n[client.transport.websocket] \ntls = false\n\n[client.services.echo] \nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2333\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"websocket\" \n[server.transport.websocket] \ntls = false\n\n[server.services.echo] \nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_udp/noise_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"noise\" \n[client.transport.noise]\nremote_public_key = \"mEnUEACy9UrTBmwoCJb6fcKWBRdvfD9XzuBVsroOLFg=\"\n\n[client.services.echo] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"noise\" \n[server.transport.noise]\nlocal_private_key = \"kQiSRtS3bs8BoGCJYgFnl1FLrTG1lV53Dj8jSjmg8tE=\"\n\n[server.services.echo] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_udp/tcp_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"tcp\" \n\n[client.services.echo] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"tcp\" \n\n[server.services.echo] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_udp/tls_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"tls\" \n[client.transport.tls]\ntrusted_root = \"examples/tls/rootCA.crt\"\nhostname = \"localhost\"\n\n[client.services.echo] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"tls\" \n[server.transport.tls]\npkcs12 = \"examples/tls/identity.pfx\"\npkcs12_password = \"1234\"\n\n[server.services.echo] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_udp/websocket_tls_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"websocket\"\n[client.transport.tls]\ntrusted_root = \"examples/tls/rootCA.crt\"\nhostname = \"localhost\"\n[client.transport.websocket] \ntls = true\n\n[client.services.echo] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"websocket\" \n[server.transport.tls]\npkcs12 = \"examples/tls/identity.pfx\"\npkcs12_password = \"1234\"\n[server.transport.websocket] \ntls = true\n\n[server.services.echo] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/for_udp/websocket_transport.toml",
    "content": "[client]\nremote_addr = \"127.0.0.1:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[client.transport]\ntype = \"websocket\"\n[client.transport.websocket] \ntls = false\n\n[client.services.echo] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8080\" \n[client.services.pingpong] \ntype = \"udp\"\nlocal_addr = \"127.0.0.1:8081\" \n\n[server]\nbind_addr = \"0.0.0.0:2332\" \ndefault_token = \"default_token_if_not_specify\" \n\n[server.transport]\ntype = \"websocket\" \n[server.transport.websocket] \ntls = false\n\n[server.services.echo] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2334\" \n[server.services.pingpong] \ntype = \"udp\"\nbind_addr = \"0.0.0.0:2335\" \n"
  },
  {
    "path": "tests/integration_test.rs",
    "content": "use anyhow::{Ok, Result};\nuse common::{run_rathole_client, PING, PONG};\nuse rand::Rng;\nuse std::time::Duration;\nuse tokio::{\n    io::{AsyncReadExt, AsyncWriteExt},\n    net::{TcpStream, UdpSocket},\n    sync::broadcast,\n    time,\n};\nuse tracing::{debug, info, instrument};\nuse tracing_subscriber::EnvFilter;\n\nuse crate::common::run_rathole_server;\n\nmod common;\n\nconst ECHO_SERVER_ADDR: &str = \"127.0.0.1:8080\";\nconst PINGPONG_SERVER_ADDR: &str = \"127.0.0.1:8081\";\nconst ECHO_SERVER_ADDR_EXPOSED: &str = \"127.0.0.1:2334\";\nconst PINGPONG_SERVER_ADDR_EXPOSED: &str = \"127.0.0.1:2335\";\nconst HITTER_NUM: usize = 4;\n\n#[derive(Clone, Copy, Debug)]\nenum Type {\n    Tcp,\n    Udp,\n}\n\nfn init() {\n    let level = \"info\";\n    let _ = tracing_subscriber::fmt()\n        .with_env_filter(\n            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::from(level)),\n        )\n        .try_init();\n}\n\n#[tokio::test]\nasync fn tcp() -> Result<()> {\n    init();\n\n    // Spawn a echo server\n    tokio::spawn(async move {\n        if let Err(e) = common::tcp::echo_server(ECHO_SERVER_ADDR).await {\n            panic!(\"Failed to run the echo server for testing: {:?}\", e);\n        }\n    });\n\n    // Spawn a pingpong server\n    tokio::spawn(async move {\n        if let Err(e) = common::tcp::pingpong_server(PINGPONG_SERVER_ADDR).await {\n            panic!(\"Failed to run the pingpong server for testing: {:?}\", e);\n        }\n    });\n\n    test(\"tests/for_tcp/tcp_transport.toml\", Type::Tcp).await?;\n\n    #[cfg(any(\n         // FIXME: Self-signed certificate on macOS nativetls requires manual interference.\n         all(target_os = \"macos\", feature = \"rustls\"),\n         // On other OS accept run with either\n         all(not(target_os = \"macos\"), any(feature = \"native-tls\", feature = \"rustls\")),\n     ))]\n    test(\"tests/for_tcp/tls_transport.toml\", Type::Tcp).await?;\n\n    #[cfg(feature = \"noise\")]\n    test(\"tests/for_tcp/noise_transport.toml\", Type::Tcp).await?;\n\n    #[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\n    test(\"tests/for_tcp/websocket_transport.toml\", Type::Tcp).await?;\n\n    #[cfg(not(target_os = \"macos\"))]\n    #[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\n    test(\"tests/for_tcp/websocket_tls_transport.toml\", Type::Tcp).await?;\n\n    Ok(())\n}\n\n#[tokio::test]\nasync fn udp() -> Result<()> {\n    init();\n\n    // Spawn a echo server\n    tokio::spawn(async move {\n        if let Err(e) = common::udp::echo_server(ECHO_SERVER_ADDR).await {\n            panic!(\"Failed to run the echo server for testing: {:?}\", e);\n        }\n    });\n\n    // Spawn a pingpong server\n    tokio::spawn(async move {\n        if let Err(e) = common::udp::pingpong_server(PINGPONG_SERVER_ADDR).await {\n            panic!(\"Failed to run the pingpong server for testing: {:?}\", e);\n        }\n    });\n\n    test(\"tests/for_udp/tcp_transport.toml\", Type::Udp).await?;\n\n    #[cfg(any(\n         // FIXME: Self-signed certificate on macOS nativetls requires manual interference.\n         all(target_os = \"macos\", feature = \"rustls\"),\n         // On other OS accept run with either\n         all(not(target_os = \"macos\"), any(feature = \"native-tls\", feature = \"rustls\")),\n     ))]\n    test(\"tests/for_udp/tls_transport.toml\", Type::Udp).await?;\n\n    #[cfg(feature = \"noise\")]\n    test(\"tests/for_udp/noise_transport.toml\", Type::Udp).await?;\n\n    #[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\n    test(\"tests/for_udp/websocket_transport.toml\", Type::Udp).await?;\n\n    #[cfg(not(target_os = \"macos\"))]\n    #[cfg(any(feature = \"websocket-native-tls\", feature = \"websocket-rustls\"))]\n    test(\"tests/for_udp/websocket_tls_transport.toml\", Type::Udp).await?;\n\n    Ok(())\n}\n\n#[instrument]\nasync fn test(config_path: &'static str, t: Type) -> Result<()> {\n    if cfg!(not(all(feature = \"client\", feature = \"server\"))) {\n        // Skip the test if the client or the server is not enabled\n        return Ok(());\n    }\n\n    let (client_shutdown_tx, client_shutdown_rx) = broadcast::channel(1);\n    let (server_shutdown_tx, server_shutdown_rx) = broadcast::channel(1);\n\n    // Start the client\n    info!(\"start the client\");\n    let client = tokio::spawn(async move {\n        run_rathole_client(config_path, client_shutdown_rx)\n            .await\n            .unwrap();\n    });\n\n    // Sleep for 1 second. Expect the client keep retrying to reach the server\n    time::sleep(Duration::from_secs(1)).await;\n\n    // Start the server\n    info!(\"start the server\");\n    let server = tokio::spawn(async move {\n        run_rathole_server(config_path, server_shutdown_rx)\n            .await\n            .unwrap();\n    });\n    time::sleep(Duration::from_millis(2500)).await; // Wait for the client to retry\n\n    info!(\"echo\");\n    echo_hitter(ECHO_SERVER_ADDR_EXPOSED, t).await.unwrap();\n    info!(\"pingpong\");\n    pingpong_hitter(PINGPONG_SERVER_ADDR_EXPOSED, t)\n        .await\n        .unwrap();\n\n    // Simulate the client crash and restart\n    info!(\"shutdown the client\");\n    client_shutdown_tx.send(true)?;\n    let _ = tokio::join!(client);\n\n    info!(\"restart the client\");\n    let client_shutdown_rx = client_shutdown_tx.subscribe();\n    let client = tokio::spawn(async move {\n        run_rathole_client(config_path, client_shutdown_rx)\n            .await\n            .unwrap();\n    });\n    time::sleep(Duration::from_secs(1)).await; // Wait for the client to start\n\n    info!(\"echo\");\n    echo_hitter(ECHO_SERVER_ADDR_EXPOSED, t).await.unwrap();\n    info!(\"pingpong\");\n    pingpong_hitter(PINGPONG_SERVER_ADDR_EXPOSED, t)\n        .await\n        .unwrap();\n\n    // Simulate the server crash and restart\n    info!(\"shutdown the server\");\n    server_shutdown_tx.send(true)?;\n    let _ = tokio::join!(server);\n\n    info!(\"restart the server\");\n    let server_shutdown_rx = server_shutdown_tx.subscribe();\n    let server = tokio::spawn(async move {\n        run_rathole_server(config_path, server_shutdown_rx)\n            .await\n            .unwrap();\n    });\n    time::sleep(Duration::from_millis(2500)).await; // Wait for the client to retry\n\n    // Simulate heavy load\n    info!(\"lots of echo and pingpong\");\n\n    let mut v = Vec::new();\n\n    for _ in 0..HITTER_NUM / 2 {\n        v.push(tokio::spawn(async move {\n            echo_hitter(ECHO_SERVER_ADDR_EXPOSED, t).await.unwrap();\n        }));\n\n        v.push(tokio::spawn(async move {\n            pingpong_hitter(PINGPONG_SERVER_ADDR_EXPOSED, t)\n                .await\n                .unwrap();\n        }));\n    }\n\n    for h in v {\n        assert!(tokio::join!(h).0.is_ok());\n    }\n\n    // Shutdown\n    info!(\"shutdown the server and the client\");\n    server_shutdown_tx.send(true)?;\n    client_shutdown_tx.send(true)?;\n\n    let _ = tokio::join!(server, client);\n\n    Ok(())\n}\n\nasync fn echo_hitter(addr: &'static str, t: Type) -> Result<()> {\n    match t {\n        Type::Tcp => tcp_echo_hitter(addr).await,\n        Type::Udp => udp_echo_hitter(addr).await,\n    }\n}\n\nasync fn pingpong_hitter(addr: &'static str, t: Type) -> Result<()> {\n    match t {\n        Type::Tcp => tcp_pingpong_hitter(addr).await,\n        Type::Udp => udp_pingpong_hitter(addr).await,\n    }\n}\n\nasync fn tcp_echo_hitter(addr: &'static str) -> Result<()> {\n    let mut conn = TcpStream::connect(addr).await?;\n\n    let mut wr = [0u8; 1024];\n    let mut rd = [0u8; 1024];\n    for _ in 0..100 {\n        rand::thread_rng().fill(&mut wr);\n        conn.write_all(&wr).await?;\n        conn.read_exact(&mut rd).await?;\n        assert_eq!(wr, rd);\n    }\n\n    Ok(())\n}\n\nasync fn udp_echo_hitter(addr: &'static str) -> Result<()> {\n    let conn = UdpSocket::bind(\"127.0.0.1:0\").await?;\n    conn.connect(addr).await?;\n\n    let mut wr = [0u8; 128];\n    let mut rd = [0u8; 128];\n    for _ in 0..3 {\n        rand::thread_rng().fill(&mut wr);\n\n        conn.send(&wr).await?;\n        debug!(\"send\");\n\n        conn.recv(&mut rd).await?;\n        debug!(\"recv\");\n\n        assert_eq!(wr, rd);\n    }\n    Ok(())\n}\n\nasync fn tcp_pingpong_hitter(addr: &'static str) -> Result<()> {\n    let mut conn = TcpStream::connect(addr).await?;\n\n    let wr = PING.as_bytes();\n    let mut rd = [0u8; PONG.len()];\n\n    for _ in 0..100 {\n        conn.write_all(wr).await?;\n        conn.read_exact(&mut rd).await?;\n        assert_eq!(rd, PONG.as_bytes());\n    }\n\n    Ok(())\n}\n\nasync fn udp_pingpong_hitter(addr: &'static str) -> Result<()> {\n    let conn = UdpSocket::bind(\"127.0.0.1:0\").await?;\n    conn.connect(&addr).await?;\n\n    let wr = PING.as_bytes();\n    let mut rd = [0u8; PONG.len()];\n\n    for _ in 0..3 {\n        conn.send(wr).await?;\n        debug!(\"ping\");\n\n        conn.recv(&mut rd).await?;\n        debug!(\"pong\");\n\n        assert_eq!(rd, PONG.as_bytes());\n    }\n\n    Ok(())\n}\n"
  }
]